【问题标题】:Cache performance缓存性能
【发布时间】:2015-10-22 17:38:09
【问题描述】:

我正在浏览 CSAPP 书籍,我不确定以下两个循环在缓存性能方面的区别:

这里的缓存有2048字节,直接映射(行数为1),有16字节的块,我们定义如下结构:

struct algae_position {
    int x;
    int y;
};
struct algae_position grid[16][16];

代码 1:

for (i = 0; i < 16; i++) {
    for (j = 0; j < 16; j++) {
        total_x += grid[i][j].x;
    }
}
for (i = 0; i < 16; i++) {
    for (j = 0; j < 16; j++) {
        total_y += grid[i][j].y;
    }
}

和代码 2:

for (i = 0; i < 16; i++) {
    for (j = 0; j < 16; j++) {
        total_x += grid[i][j].x;
        total_y += grid[i][j].y;
    }
}

我的想法:我们总是有miss,hit,miss,hit的模式,因为一条线只能容纳两个网格元素。因此我们总是有 50% 的失误率。

但是根据本书,代码 2 将有 25% 的未命中率,因为缓存可以保存整个网格数组。我该如何理解这个问题?

【问题讨论】:

    标签: c caching


    【解决方案1】:

    假设 4 字节 ints,那么每个 struct algae_position 长度为 8 个字节,每个缓存行可以包含两个结构。还假设数组在缓存行边界上对齐(从缓存行的开头开始)。

    在第一个代码中,在第一个循环中,所有对 grid[i][j].x 的访问,即使是 j (0, 2, 4, ..) 都是未命中的,后面的访问是 (奇数 j, 1, 3, 5, ..) 是命中。 第一个循环有 50% 的缓存命中率。 但是,因为整个数组都适合缓存,所以第二个循环中的访问永远不会丢失,第二个循环有 100% 的缓存命中率。因此,第一个代码的缓存命中率总体为 75%。

    (实际上,至少在某些时候,一些数据最终会提前从缓存中清除,因此该代码的实际缓存命中率在 50% 到 75% 之间。)

    在第二个代码中,第一次访问,grid[0][0].x 是未命中。但是,这会导致整个高速缓存行被读入内存,因此grid[0][0].ygrid[0][1].xgrid[0][1].y 都是命中。下一次访问又是一次未命中,因此这种模式是四次访问,初始未命中,然后重复三次命中。因此,第二个代码的缓存命中率总体为 75%。缓存大小在这里无关紧要。

    在实践中,第二个代码更好(对于较大的数组来说明显更好)。它不仅只依赖于一条缓存线(加上预测,当前的 CPU 非常擅长),而且它还计算间隔期间的其他三个值,否则这些值将被浪费在等待缓存线加载。第一个代码不仅浪费时间,而且还依赖于 CPU 不中断函数,并且在整个循环中保持所有数据缓存。因此,不仅第一个代码“浪费”了缓存(依赖于大量可用的缓存),而且在等待下一个缓存行被加载时,它还没有做它可以做的工作,从而浪费 CPU 周期。

    这些延迟问题是我希望更多程序员关注的问题。

    【讨论】:

      猜你喜欢
      • 2011-11-08
      • 1970-01-01
      • 1970-01-01
      • 2012-04-29
      • 2017-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-16
      相关资源
      最近更新 更多