xak*_*p35 4 x86 caching sse prefetch dot-product
我试图矢量化循环,计算大浮点矢量的点积.我正在并行计算它,利用CPU拥有大量XMM寄存器的事实,如下所示:
__m128* A, B;
__m128 dot0, dot1, dot2, dot3 = _mm_set_ps1(0);
for(size_t i=0; i<1048576;i+=4) {
dot0 = _mm_add_ps( dot0, _mm_mul_ps( A[i+0], B[i+0]);
dot1 = _mm_add_ps( dot1, _mm_mul_ps( A[i+1], B[i+1]);
dot2 = _mm_add_ps( dot2, _mm_mul_ps( A[i+2], B[i+2]);
dot3 = _mm_add_ps( dot3, _mm_mul_ps( A[i+3], B[i+3]);
}
... // add dots, then shuffle/hadd result.
Run Code Online (Sandbox Code Playgroud)
我听说使用预取指令可以帮助加速,因为它可以"在后台"获取更多数据,同时执行muls并添加缓存中的数据.但是我没有找到关于如何使用_mm_prefetch()的示例和解释,何时使用什么地址和什么命中.你可以帮忙吗?
Bee*_*ope 11
可能适用于像你这样完美线性流循环的简短答案可能是:根本不使用它们,让硬件预取器完成工作.
不过,这是可能的,你可以加快速度与软件预取,这里是理论和一些细节,如果你想尝试...
基本上你_mm_prefetch()
在将来的某个时候打电话给你需要的地址.它在某些方面类似于从内存中加载一个值并且不对它做任何事情:两者都将该行带入L1高速缓存2中,但是预取内在函数(在封面下发出特定的预取指令)具有一些优点,使其适用用于预取.
它适用于缓存行粒度1:您只需要为每个缓存行发出一个预取:更多只是浪费.这意味着通常,您应该尝试展开循环,以便每个缓存行只能发出一个预取.在16字节__m128
值的情况下,这意味着至少展开4(你已经完成了,所以你在那里很好).
然后简单地PF_DIST
在当前计算之前预取每个访问流一段距离,例如:
for(size_t i=0; i<1048576;i+=4) {
dot0 = _mm_add_ps( dot0, _mm_mul_ps( A[i+0], B[i+0]);
dot1 = _mm_add_ps( dot1, _mm_mul_ps( A[i+1], B[i+1]);
dot2 = _mm_add_ps( dot2, _mm_mul_ps( A[i+2], B[i+2]);
dot3 = _mm_add_ps( dot3, _mm_mul_ps( A[i+3], B[i+3]);
_mm_prefetch(A + i + PF_A_DIST, HINT_A);
_mm_prefetch(B + i + PF_B_DIST, HINT_B);
}
Run Code Online (Sandbox Code Playgroud)
这PF_[A|B]_DIST
是在当前迭代之前预取的距离,HINT_
是使用的时间提示.我不是试图从第一原理计算正确的距离值,而是简单地确定PF_[A|B]_DIST
实验的良好值4.为了减少搜索空间,您可以先将它们设置为相等,因为逻辑上类似的距离可能是理想的.您可能会发现只预取两个流中的一个是理想的.
理想情况PF_DIST
取决于硬件配置,这一点非常重要.不仅在CPU模型上,而且在内存配置上,包括诸如多插槽系统的窥探模式之类的细节.例如,同一CPU系列的客户端和服务器芯片上的最佳值可能会大不相同.因此,您应该尽可能在您定位的实际硬件上运行调整实验.如果您针对各种硬件,您可以在所有硬件上进行测试,并希望找到一个对所有硬件都有好处的值,或者甚至考虑编译时或运行时调度,具体取决于CPU类型(并不总是如上所述)或基于CPU类型在运行时测试上.现在只是依靠硬件预取开始听起来好多了,不是吗?
你可以使用相同的方法找到最好的,HINT
因为搜索空间很小(只有4个值可以尝试) - 但是你应该知道,不同提示之间的差异(特别是_MM_HINT_NTA
)可能只显示为代码中的性能差异在此循环之后运行,因为它们会影响与此内核无关的数据保留在缓存中.
您可能还会发现这种预取根本没有用,因为您的访问模式是完全线性的,并且可能由L2流预取程序很好地处理.还有一些你可以尝试或考虑的额外的,更硬的代码:
PF_DIST
窗口内)预取所有数据,并且在循环结束时您将会预取额外的数据并PF_DIST
超出数组的末尾.这些废物获取和指令带宽最多,但它们也可能导致(最终丢弃)可能影响性能的页面错误.你可以通过特殊的介绍和outro循环来解决这些问题.我还强烈推荐这篇由5部分组成的博客文章" 优化AMD Opteron内存带宽",该文章介绍了如何优化与您的问题非常相似的问题,并详细介绍了预取(它给了大量的提升).现在这是完全不同的硬件(AMD Opteron),它可能与更近期的硬件(特别是英特尔硬件,如果你正在使用的那样)表现不同 - 但改进的过程是关键,作者是该领域的专家.
1它实际上可能工作在2-cache-line粒度之类,具体取决于它与相邻缓存行预取器的交互方式.在这种情况下,您可以通过发出预取数量的一半来逃避:每128个字节一个.
2在软件预取的情况下,您还可以使用时间提示选择其他级别的缓存.
3有一些迹象表明即使有完美的流媒体负载,并且尽管现代英特尔硬件中存在"下一页预取器",页面边界仍然是硬件预取的障碍,可以通过软件预取来部分缓解.也许是因为软件预取提供了一个更强的暗示:"是的,我将读入此页面",或者因为软件预取工作在虚拟地址级别并且必然涉及翻译机制,而L2预取在物理级别工作.
4请注意,由于我计算地址的方式,PF_DIST
值的"单位" 是sizeof(__mm128)
16个字节.
归档时间: |
|
查看次数: |
1612 次 |
最近记录: |