7

我刚刚写了一个程序来测试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 语句

4

2 回答 2

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 缓存读取未命中数是 的 4 倍bar,而 4 只是NUM_THREADS.

正如@Mysticial 所回答的那样

顺序内存访问几乎总是会胜过非顺序访问。

由于更多的非顺序内存访问意味着更多的缓存未命中。

于 2013-01-24T05:57:39.140 回答
1

顺序访问快得多的原因不是由于缓存结构,而是由于硬件预取(这是相关的,但不一样)。有几个“流式”预取器可以识别流或基于步幅的访问模式,并提前为您预取数据。

一些示例(英特尔 CPU,但类似的原理也常用于其他 CPU): http: //software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using -硬件实现的预取器

这里建议的 valgrind 分析证明了这一点,我建议也查看 L2/L3 - 在大型数据集上,有用的预取更有可能驻留在那里(经验法则 - 你离核心越远,时间和可用于积极预取的存储)。

于 2013-01-28T21:41:13.987 回答