C ++如何强制预取数据缓存? (数组循环)

时间:2013-01-09 21:37:47

标签: c++ cpu-cache prefetch

我有这样的循环

start = __rdtsc();
unsigned long long count = 0;
for(int i = 0; i < N; i++)
    for(int j = 0; j < M; j++)
        count += tab[i][j];
stop = __rdtsc();
time = (stop - start) * 1/3;

需要检查预取数据如何影响效率。如何在计算内存之前强制从内存中预取一些值?

4 个答案:

答案 0 :(得分:7)

仅限GCC:

__builtin_prefetch((const void*)(prefetch_address),0,0);

prefetch_address可能无效,不会出现段错误。如果prefetch_address与当前位置之间的差异太小,则可能没有影响甚至减速。尽量设置至少1k。

答案 1 :(得分:4)

首先,我认为tab是一个大的二维数组,例如静态数组(例如int tab[1024*1024][1024*1024])或动态分配的数组(例如int** tab并跟随{{ 1}}为s)。在这里,您希望从malloc预取一些数据到缓存以减少执行时间。

简单地说,我认为您不需要手动将任何预取插入到代码中,只需执行2D阵列的简单缩减即可。如有必要,现代CPU将进行自动预取并且有利可图。


你应该知道这个问题的两个事实:

(1)您已经在最内层循环内部利用tab的空间局部性。一旦读取tab(在高速缓存未命中或页面错误之后),tab[i][0]tab[i][0]的数据将在您的CPU高速缓存中,假设高速缓存行大小为64字节

(2)但是,当代码遍历行时,即tab[i][15]tab[i][M-1]时,很可能发生冷缓存未命中,特别是当tab[i+1][0]是动态分配的数组,其中每行可以以分段方式分配。但是,如果静态分配数组,则每行将在内存中连续定位。


因此,只有在您提前阅读(1)下一行的第一项和(2)tab时,预取才有意义。

您可以通过在上部循环中插入预取操作(例如,j + CACHE_LINE_SIZE/sizeof(tab[0][0]))来实现。但是,现代编译器可能并不总是发出这样的预取指令。如果你真的想这样做,你应该检查生成的二进制代码。

但是,正如我所说,我建议您这样做,因为现代CPU主要会自动执行预取,并且自动预取将主要优于您的手动代码。例如,像Ivy Bridge处理器这样的Intel CPU,有多个数据预取程序,例如预取到L1,L2或L3缓存。 (我不认为移动处理器有一个奇特的数据预取器)。一些预取程序将加载相邻的缓存行。


如果您在大型2D阵列上进行更昂贵的计算,则有许多替代算法对缓存更友好。一个值得注意的例子是阻塞(标题)矩阵乘法。朴素矩阵乘法会遭受大量缓存未命中,但是通过计算适合缓存的小子集,阻塞算法可以显着减少缓存未命中。请参阅this等参考文献。

答案 2 :(得分:3)

最简单/最便携的方法是简单地读取每个缓存行字节数据。假设tab是一个正确的二维数组,你可以:

char *tptr = (char *)&tab[0][0];
tptr += 64;
char temp;
volatile char keep_temp_alive;
for(int i = 0; i < N; i++)
{
    temp += *tptr;
    tptr += 64;
    for(j = 0; j < M; j++)
        count += tab[i][j];
}
keep_temp_alive = temp;

这样的事情。但是,它确实取决于:  你不会在分配的内存之外阅读[太多]。  2. J循环不比64 butes大得多。如果是,您可能希望在循环开始时添加更多temp += *tptr; tptr += 64;步骤。

循环之后的keep_temp_alive对于防止编译器完全删除temp作为不必要的负载是必不可少的。

不幸的是,我编写通用代码以建议内置指令的速度太慢,其中的重点是Leonid。

答案 3 :(得分:0)

__builtin_prefetch指令非常有用,但是特定于clang / gcc。如果要编译到多个编译器目标,那么我很幸运地将x86内在的_mm_prefetch与clang和MSVC一起使用。

https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_prefetch