gpt4 book ai didi

caching - 如何正确使用预取指令?

转载 作者:行者123 更新时间:2023-12-02 10:09:02 27 4
gpt4 key购买 nike

我试图向量化一个循环,计算大浮点 vector 的点积。我利用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.

我听说使用预取指令可以帮助加快处理速度,因为它可以在做“muls”操作并添加缓存中的数据时“在后台”获取更多数据。但是我找不到关于如何使用_mm_prefetch()的示例和说明,何时,使用什么地址和点击。你能帮上忙吗?

最佳答案

对于像您这样的完美线性流循环而言,可能适用的简短答案可能是:根本不使用它们,让硬件预取器完成工作。

尽管如此,您仍然可以通过软件预取来加快处理速度,如果您想尝试的话,这里是理论和一些细节...

基本上,您在将来某个时候需要的地址上调用_mm_prefetch()。在某些方面,这类似于从内存加载值并且不对其执行任何操作:两者都将行带入L1 cache2,但是在内部发出特定prefetch instructions的预取内在函数具有一些优势,使其适合于预取。 。

它以高速缓存行粒度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);
}

这里 PF_[A|B]_DIST是在当前迭代之前要预取的距离,而 HINT_是要使用的时间提示。与其尝试根据第一原理来计算正确的距离值,不如说是通过实验确定 PF_[A|B]_DIST的合适值。为了减少搜索空间,您可以先将它们设置为相等,因为从逻辑上讲,相似的距离可能是理想的选择。您可能会发现,仅预取两个流之一是理想的。

理想的 PF_DIST 取决于硬件配置非常重要。不仅在CPU型号上,而且在内存配置上,包括详细信息,例如多插槽系统的侦听模式。例如,在同一CPU系列的客户端和服务器芯片上,最佳值(value)可能会大不相同。因此,您应该尽可能在目标硬件上运行调整实验。如果您针对各种硬件,则可以在所有硬件上进行测试,并希望找到一个对所有硬件都适用的值,甚至可以根据CPU类型(如上所述并非总是足够)或基于在运行时测试中。现在,仅依靠硬件预取已开始听起来好多了,不是吗?

由于搜索空间很小(只能尝试4个值),因此您可以使用相同的方法找到最佳的 HINT-但是在这里您应该意识到,不同提示之间的差异(尤其是 _MM_HINT_NTA)可能仅表现为此循环后运行的代码,因为它们会影响与该内核无关的数据保留在缓存中。

您可能还会发现这种预取根本没有帮助,因为您的访问模式是完全线性的,并且很可能由L2流预取器很好地处理。您仍然可以尝试或考虑一些其他的,更多的硬编码内容:
  • 您可能会调查仅在4K页面边界的开头进行预取是否有帮助3。这将使循环结构变得复杂:您可能需要一个嵌套循环来分隔“页面附近”和“页面内部”情况,以便仅在页面边界附近发出预取。您还需要使输入数组也页面对齐,否则它将变得更加复杂。
  • 您可以尝试disabling some/all of the hardware prefetchers。这通常对于整体性能来说很糟糕,但是在软件预取的高度调整的负载下,通过消除硬件预取的干扰,您可能会看到更好的性能。选择禁用预取还为您提供了一个重要的关键工具,可帮助您了解正在发生的事情,即使最终最终使所有预取器都处于启用状态也是如此。
  • 确保您使用的是大页面,因为对于像这样的大型连续块,这是个主意。
  • 在主计算循环的开始和结束时存在预取问题:在开始时,您会错过在每个数组的开始(在初始PF_DIST窗口内)以及在循环结束时预取所有数据的机会。将在数组末尾预取其他代码和PF_DIST。这些浪费充其量是指令充其量,但它们也可能导致(最终被丢弃的)页面错误,从而可能会影响性能。您可以通过特殊的intro和outro循环进行修复,以处理这些情况。

  • 我还强烈推荐博客文章 Optimizing AMD Opteron Memory Bandwidth(分为5部分),该文章描述了与您的问题非常相似的优化问题,并涵盖了预取的详细信息(这对您有很大帮助)。现在,这是完全不同的硬件(AMD Opteron),其行为可能与较新的硬件(特别是如果您使用的是Intel硬件)有所不同-但是改进过程很关键,并且作者是该 Realm 的专家。

    1实际上,它可能会以类似于2个缓存行的粒度的方式工作,具体取决于它与相邻缓存行预取器的交互方式。在这种情况下,您可以通过发出一半的预取来摆脱困境:每128字节发送一次。

    2在软件预取的情况下,您还可以使用时间提示来选择其他级别的缓存。

    3有迹象表明,即使具有完美的流负载,并且尽管现代英特尔硬件中存在“下一页预取器”,但页面边界仍然是硬件预取的障碍,可以通过软件预取来部分缓解。可能是因为软件预取可以更好地暗示“是的,我要阅读此页面”,或者是因为软件预取可以在虚拟地址级别工作,并且必然涉及转换机制,而L2预取则可以在物理级别工作。

    4请注意,由于我计算地址的方式, PF_DIST值的“单位”为 sizeof(__mm128),即16个字节。

    关于caching - 如何正确使用预取指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48994494/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com