C++二维数组的访问效率

Access efficiency of C++ 2D array

我有一个二维数组 a1[10000][100],有 10000 行和 100 列,还有一个二维数组 a2[100][10000],它是 a1.

的转置矩阵

现在我需要按 a1[0][20]a1[0][70]a1[1][20] 的顺序访问 a1 的 2 列(例如第 21 列和第 71 列), a1[1][70]、...、a1[9999][20]a1[9999][70]。或者我也可以访问 a2 来实现相同的目标(顺序:a2[20][0]a2[70][0]a2[20][1]a2[70][1]、...、a2[20][9999], a2[70][9999]).但后者比前者快得多。相关代码简化如下(size1 = 10000):

1  sum1 = 0;
2  for (i = 0; i < size1; ++i) {
3      x = a1[i][20];
4      y = a1[i][70];
5      sum1 = x + y;
6  } // this loop is slower
7  a_sum1[i] = sum1;
8
9  sum2 = 0;
10 for (i = 0; i < size1; ++i) {
11     x = a2[20][i];
12     y = a2[70][i];
14     sum2 = x + y;
15 } // this loop is faster
16 a_sum2[i] = sum2;

访问 a2 的更多行(我也尝试过 3、4 行而不是上面示例中的 2 行)也比访问相同数量的 a1 的列更快。当然我也可以把第3-5行(或第11-14行)换成一个循环(通过使用一个额外的数组来存储要访问的column/row索引),也得到与后者相同的结果比前者快。

为什么后者比前者快很多?我对高速缓存行有所了解,但我不知道这种情况的原因。谢谢。

这是因为 C++ 具有行优先顺序 (https://en.wikipedia.org/wiki/Row-_and_column-major_order). You should avoid column-major access in C/C++ (https://www.appentra.com/knowledge/checks/pwr010/)。

原因是元素按行存储,按行访问可以更好地使用缓存行、矢量化和其他硬件features/techniques。

如果您在短时间内访问同一缓存行中的地址,则可以从内存缓存中受益。下面的解释假定您的数组包含 4 字节整数。

在你的第一个循环中,你在循环中的两次内存访问相隔50*4字节,下一次迭代向前跳转400字节。这里的每个内存访问都是缓存未命中。

在第二个循环中,您仍然有两次相隔 50*400 字节的内存访问,但在下一个循环迭代中,您访问的地址紧邻先前获取的值。假设常见的 64 字节缓存行大小,每 16 次循环迭代只有两次缓存未命中,其余的可以从这样一个循环开始时加载的两个缓存行中提供。

原因是缓存局部性。

a2[20][0], a2[20][1], a2[20][2] ... 在内存中并排存储。而 a1[0][20]a1[1][20]a1[2][20] ... 不是(同样适用于 a2[70][0]a2[70][1]a2[70][2] ...) .

这意味着访问 a1[0][20]a1[1][20]a1[2][20] 会浪费 DRAM 带宽,因为它只会使用从 DRAM 加载的每个 64 字节缓存行中的 4 或 8 个字节.