【问题标题】:Impacts of CPU cache on speedCPU缓存对速度的影响
【发布时间】:2013-01-07 18:36:48
【问题描述】:

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

【问题讨论】:

  • 那个 if 语句看起来与分支预测器问题的那个惊人地相似。有必要拥有吗?
  • @Rohan 和 @Mysticial,foo()bar() 在同一个随机数组上运行。
  • 顺序内存访问几乎总是优于非顺序访问。我不知道还能说什么。 foo() 是非连续的,bar() 是连续的。我不确定 if 语句的目的是什么。但是,如果它以接近 50% 的概率随机进入,那么这可能会给分支错误预测的结果添加“噪音”。
  • 我只是想做类似@Rohan提到的经典问题,计算数组中大于阈值的元素个数。

标签: c caching optimization pthreads cpu-architecture


【解决方案1】:

事实证明一点也不神秘。

我尝试使用 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 所回答的那样

顺序内存访问几乎总是优于非顺序访问。

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

【讨论】:

  • 现在是一个令人震惊的结论......
【解决方案2】:

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

一些示例(Intel CPU,但类似的原理也常用于其他 CPU): http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers

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

【讨论】:

    猜你喜欢
    • 2015-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多