我刚刚写了一个程序来测试CPU缓存对速度性能的影响。
void* foo(void* ptr)
{
int* r = (int*) ptr;
for (int i = (r - s); i < N; i += NUM_THREADS)
*r += num[i];
return NULL;
}
void* bar(void* ptr)
{
int* r = (int*) ptr;
int idx = r - s;
int block = N/NUM_THREADS;
int start = idx * block, end = start + block;
for (int i = start; i < end; ++i)
*r += num[i];
return NULL;
}
基本上,foo()
进行了隔行扫描,另一方面,bar()
逐块扫描数组。
测试结果表明bar()
要快得多:
gcc ping-pong.c -std=gnu99 -lpthread -O2 ; ./a.out
1.077037s
0.395525s
那么如何解释这个结果?
完整的源代码位于:https://gist.github.com/4617935
更新:删除所有if语句
答案 0 :(得分:3)
事实证明这并不神秘。
我尝试了valgrind来分析缓存未命中,结果如下:
$ valgrind --tool=cachegrind --cachegrind-out-file=profile ./a.out
....
$ cg_annotate profile --auto=yes --show=D1mr --context=1
....
-- line 63 ----------------------------------------
. void* foo(void* ptr)
0 {
0 int* r = (int*) ptr;
.
0 for (int i = (r - s); i < N; i += NUM_THREADS)
16,388 *r += num[i];
0 return NULL;
0 }
.
-- line 71 ----------------------------------------
-- line 72 ----------------------------------------
. void* bar(void* ptr)
0 {
0 int* r = (int*) ptr;
0 int idx = r - s;
0 int block = N/NUM_THREADS;
0 int start = idx * block, end = start + block;
.
0 for (int i = start; i < end; ++i)
4,098 *r += num[i];
0 return NULL;
0 }
如您所见,foo()
的L1缓存读取次数是bar
的4倍,而4只是NUM_THREADS
。
正如@Mysticial回答的那样
顺序内存访问几乎总是超出非顺序访问。
由于更多非顺序内存访问意味着更多缓存未命中。
答案 1 :(得分:1)
顺序访问速度快得多的原因不是由于缓存结构,而是由于HW预取(这是相关的,但不相同)。有几个“流式”预取程序可以识别基于流或基于步幅的访问模式,并为您预先提取数据。
一些示例(英特尔CPU,但类似的主体也常用于其他CPU): http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers
这里建议的valgrind分析表明了它,我建议再看一下L2 / L3 - 在大数据集上,有用的预取更有可能驻留在那里(经验法则 - 离你的核心越远,更多的时间和存储空间,您可以进行积极的预取。)