我正在编写一个解析文件的程序。它由一个主循环组成,逐个字符地解析并处理它们。这是主循环:
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
}
正如您所看到的,我添加了一条builtin_prefetch
指令,希望在我的循环的下一次迭代中放入缓存。我尝试了不同的值:ptr+i+1
,ptr+i+2
,ptr+i+10
但似乎没有任何改变。
为了测量性能,我使用valgrind的工具cachegrind,它给出了缓存未命中数的指示。在行c = *ptr
上,当未设置__builtin_prefetch
时,cachegrind记录632,378 DLmr(L3缓存未命中)。但奇怪的是,无论我设置为__builtin_prefetch
的参数如何,此值都不会改变。
对此有何解释?
答案 0 :(得分:11)
那是因为硬件比您早多年。 :)
有一些硬件预取程序可以识别简单的模式并为您进行预取。在这种情况下,您有一个简单的顺序访问模式,这对于硬件预取器来说是微不足道的。
当您拥有硬件无法预测的访问模式时,手动预取非常方便。
以下是一个这样的例子:Prefetching Examples?
答案 1 :(得分:3)
首先,缓存交易的最小单位称为cache line
,缓存行可以是例如64字节长,但绝不能小到1字节。因此,当您要求预取时,您需要提前询问当前的兴趣位置。您需要知道缓存行大小,因此不应要求位于同一缓存行中的地址。您还需要不要多次调用预取,因为很快就可能会使用高速缓存行,并在执行指令时创建性能命中。
现代架构还有硬件预取程序的概念,根据您的访问模式可以提前为您预取数据。这应该是大多数时候创建数据访问时间和简单预取一样好。现在,SW预取只能帮助你,如果你能找到一个非常明显的预取数据的地方 - 而不是随机扩散到代码中。例如,在开始处理一段数据之前,如果您只是立即调用prefetch并访问数据,这对您无济于事。您需要尽早完成此操作并在访问数据之前进行其他设置工作。
我建议任何对此类主题感兴趣的人阅读The Software Optimization Cookbook。我一般都处理ARM架构,但我发现这本书非常宝贵。网上还有一些与此问题相关的摘录;请参阅#1和#2。
答案 2 :(得分:0)
正确的答案是:预取不能改变缓存未命中数,它只是强制它们更早发生:)