我有这样的循环
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;
需要检查预取数据如何影响效率。如何在计算内存之前强制从内存中预取一些值?
答案 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