为什么 __builtin_prefetch 在这里没有任何效果?

7

我正在编写一款用于解析文件的程序。它由一个主循环组成,逐个字符进行解析并对其进行处理。以下是主循环:

char c;
char * ptr;

for( size_t i = 0; i < size ; ++i )
{
    ptr = ( static_cast<char*>(sentenceMap) + i );
    c = *ptr;

    __builtin_prefetch( ptr + i + 1 );

   // some treatment on ptr and c   
}

正如您所见,我添加了一个指令,希望将下一次循环迭代放入缓存中。我尝试使用不同的值:ptr+i+1ptr+i+2ptr+i+10,但似乎没有任何变化。
为了衡量性能,我使用valgrind的工具cachegrind,它可以给我一个缓存未命中的数量指标。当没有设置时,在c=*ptr这一行,cachegrind记录了632,378个DLmr(L3缓存未命中)。然而奇怪的是,无论我将参数设置为什么,这个值都不会改变。
有什么解释吗?
3个回答

12
那是因为硬件比你们领先几年。 :)
有硬件预取器被设计用于识别简单的模式并为您进行预取。 在这种情况下,您具有简单的顺序访问模式,对于硬件预取器而言,这不仅仅是微不足道的。
只有当硬件无法预测访问模式时,手动预取才会派上用场。
以下是一个这样的例子: Prefetching Examples?

那太好了 :) 我正在观察GCC生成的反汇编代码,它完全忽略了预取指令。我想你是对的。 - qdii
4
GCC实际上移除了您添加的预取操作吗?我感到印象深刻! - Mysticial
实际上,不,让我收回之前的话。我在代码往下滚动一些后找到了隐藏的预取指令。优化后的代码有点凌乱 :) - qdii
旁边的问题,有什么我可以做来防止这些缓存未命中吗? - qdii
这取决于调用代码。如果数据不在缓存中,那么这些缺失无论如何都是不可避免的。在任何情况下,缓存未命中的定义在这一点上变得模糊起来。我不知道硬件计数器是否会将成功的预取作为缓存未命中计算。 - Mysticial

3
首先,缓存处理的最小单位称为“缓存行”,一个缓存行可以是64字节长,但永远不会小到1字节。因此,当您请求预取时,需要提前很多关于您当前感兴趣位置的内容。您需要知道缓存行大小,所以不应该请求位于同一缓存行中的地址。您还需要不要过多地调用预取,因为这可能会使即将使用的缓存行被驱逐,并且执行指令也会受到性能影响。
现代架构还具有硬件预取器的概念,根据访问模式,可以提前为您预取数据。这通常会创建与简单预取相同的数据访问时间。现在,如果您可以找到一个明显的地方预取数据,则SW预取只能帮助您,而不能随意散布在代码中。例如,在开始处理数据之前,但如果您只是调用预取并立即访问数据,则无法帮助您。您需要尽早完成此操作并在访问数据之前进行其他设置工作。
我建议对这个话题感兴趣的人阅读软件优化食谱。我通常处理ARM架构,但我发现这本书非常宝贵。还有一些与此问题相关的摘录可在网上找到;请参见#1#2

1
正确答案是:预取不能改变缓存未命中的数量,它只是强制它们提前发生 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接