我一直在努力了解如何编写缓存友好的代码。因此,作为第一步,我试图了解数组行主要访问和列主要访问之间的性能差异。
所以我创建了一个大小为512×512的int数组,因此总大小为1MB。我的L1缓存是32KB,L2缓存是256KB,L3缓存是3MB。所以我的阵列适合L3缓存。
我只是按行主要顺序和列主要顺序计算数组元素的总和,并比较它们的速度。一直以来,列主要订单稍快一些。我预计行主要顺序要快于其他顺序(可能快几倍)。
我认为问题可能是由于数组的小尺寸,所以我制作了另一个大小为8192×8192(256 MB)的数组。结果仍然相同。
以下是我使用的代码段:
#include "time.h"
#include <stdio.h>
#define S 512
#define M S
#define N S
int main() {
// Summing in the row major order
int x = 0;
int iter = 25000;
int i, j;
int k[M][N];
int sum = 0;
clock_t start, end;
start = clock();
while(x < iter) {
for (i = 0; i < M; i++) {
for(j = 0; j < N; j++) {
sum += k[i][j];
}
}
x++;
}
end = clock();
printf("%i\n", end-start);
// Summing in the column major order
x = 0;
sum = 0;
int h[M][N];
start = clock();
while(x < iter) {
for (j = 0; j < N; j++) {
for(i = 0; i < M; i++){
sum += k[i][j];
}
}
x++;
}
end = clock();
printf("%i\n", end-start);
}
问题:有人可以告诉我我的错误是什么以及为什么我得到这个结果?
答案 0 :(得分:10)
我真的不知道你为什么会这样做,但让我澄清一些事情。
在考虑缓存时,至少要考虑两件事:缓存大小和缓存行大小。例如,我的Intel i7 920处理器具有256KB二级高速缓存,线路大小为64字节。如果您的数据适合缓存,那么访问它的顺序并不重要。优化代码以便缓存友好的所有问题必须针对两件事:如果可能的话,以块的形式适应缓存的方式分割对块的访问。使用该块进行所有可能的计算,然后使用下一个块,使用它进行计算,依此类推。另一件事(你要做的是)以连续的方式访问内存。当您从内存中请求数据时(假设为int - 4个字节),整个缓存行被带到缓存中(在我的情况下是64个字节:这是16个相邻的整数(包括你的那个)请求)被带到缓存)。这里有播放行顺序和列顺序。对于行顺序,每16个内存请求有1个高速缓存未命中,按列顺序,每个请求都会有一个高速缓存未命中(但只有当您的数据不适合高速缓存时;如果您的数据适合高速缓存,那么您将获得与行顺序相同的比率,因为你在行中请求第一个元素时仍然有缓存中的行;当然,关联性可以起作用,即使并非所有缓存都被填充,也可以重写缓存行与您的数据)。
关于你的问题,当数据适合缓存时,正如我所说,访问顺序并不重要,但是当你进行第二次求和时,数据已经在缓存中了从你做第一笔金额时起,这就是为什么它更快。如果先进行列顺序求和,则应该看到行顺序总和变得更快,因为之后就完成了。但是,当数据足够大时,您不应该获得相同的行为。请尝试以下操作:在两个总和之间,使用其他大数据执行某些操作,以使整个缓存无效。
修改强>
我看到行专业的3-4倍加速(虽然我预计&gt; 8倍加速。任何想法为什么?)。 [..]如果你能告诉我为什么加速只是3倍
那就太好了
不是那种访问矩阵的“正确方式”并没有太大改善,更像是访问矩阵,“错误的方式”不会伤害那么多,如果这有任何意义的话。
虽然我无法为您提供具体而准确的答案,但我可以告诉您的是,现代处理器具有非常复杂且极其高效的缓存模型。它们非常强大,例如,在许多常见情况下,它们可以屏蔽缓存级别,使得看起来像3级缓存而不是一级缓存(在增加数据大小时不会受到惩罚)尺寸适合L2到仅适合L3的尺寸。在较旧的处理器中运行代码(比方说10年),您可能会看到预期的加速。然而,现代处理器具有对缓存未命中有很大帮助的机制。桌面处理器的设计理念是快速运行“坏代码”,因此在改善“坏代码”性能方面投入了大量资金,因为绝大多数桌面应用程序不是由理解分支问题或缓存模型的人编写的。这与高性能市场相反,专业处理器使得糟糕的代码受到很大的伤害,因为它们实现了处理“坏代码”(或根本不执行)的弱机制。这些机制占用了大量的晶体管,因此它们增加了功耗和产生的热量,但它们值得在桌面处理器中实现,其中大部分代码都是“坏代码”。