我正在阅读Ulrich Drepper 的cpumemory.pdf,但我无法理解第 6.2.1 章(第 49-50 页)中关于在矩阵乘法中优化缓存访问的以下部分:
显示了矩阵乘法的第一个简单方法:
for (i = 0; i < N; ++i)
for (j = 0; j < N; ++j)
for (k = 0; k < N; ++k)
res[i][j] += mul1[i][k] * mul2[k][j];
mul2
由列访问,因此对于每一列,一个缓存行被浪费了。乌尔里希 说:
sizeof(double) 为 8 这意味着,为了充分利用缓存行,我们应该展开中间循环 8 次。
为简洁起见,我只展开了 2 次中间循环。
for (i = 0; i < N; ++i)
for (j = 0; j < N; j += 2)
for (k = 0; k < N; ++k) {
res[i][j+0] += mul1[i][k] * mul2[k][j+0];
res[i][j+1] += mul1[i][k] * mul2[k][j+1];
}
现在很明显,如果缓存行的宽度为 2 个双精度值,它将被充分利用。但随后乌尔里希继续说道:
继续这个想法,为了有效地使用 res 矩阵,即同时写入 8 个结果,我们也应该将外循环展开 8 次。
为简洁起见,我再次展开外循环 2 次。
for (i = 0; i < N; i += 2)
for (j = 0; j < N; j+=2)
for (k = 0; k < N; ++k) {
res[i+0][j+0] += mul1[i+0][k] * mul2[k][j+0];
res[i+0][j+0] += mul1[i+0][k] * mul2[k][j+0];
res[i+1][j+0] += mul1[i+1][k] * mul2[k][j+0];
res[i+1][j+1] += mul1[i+1][k] * mul2[k][j+1];
}
对我来说,它似乎比以前的版本更糟糕,因为现在mul1
是按列访问的。请解释乌尔里希的意思。