#define IMGX 8192
#define IMGY 8192
int red_freq[256];
char img[IMGY][IMGX][3];
main(){
int i, j;
long long total;
long long redness;
for (i = 0; i < 256; i++)
red_freq[i] = 0;
for (i = 0; i < IMGY; i++)
for (j = 0; j < IMGX; j++)
red_freq[img[i][j][0]] += 1;
total = 0;
for (i = 0; i < 256; i++)
total += (long long)i * (long long)red_freq[i];
redness = (total + (IMGX*IMGY/2))/(IMGX*IMGY);
将第二个for循环替换为
时有什么区别for (j = 0; j < IMGX; j++)
for (i = 0; i < IMGY; i++)
red_freq[img[i][j][0]] += 1;
其他一切都保持不变,为什么第一种算法比第二种算法更快?
是否与内存分配有关?
答案 0 :(得分:8)
第一个版本按顺序改变内存,因此最佳地使用处理器缓存。 第二个版本使用它加载的每个缓存行中的一个值,因此它对于缓存使用而言是很小的。
要理解的一点是缓存被划分为多行,每一行都包含整个结构中的许多值。
第一个版本也可能由编译器优化,以使用更快的指令(SIMD指令)。
答案 1 :(得分:5)
这是因为第一个版本按照物理布局的顺序迭代内存,而第二个版本在内存中从数组中的一列跳到下一列。这将导致缓存抖动并干扰CPU的最佳性能,然后必须花费大量时间等待缓存一次又一次地刷新。
答案 2 :(得分:2)
这是因为大型现代处理器架构(如PC中的架构)经过大规模优化,可以处理他们最近访问过的“接近”(与地址相关的术语)内存的内存。实际的物理内存访问比理论上运行的CPU慢得多,所以有助于进程以最有效的方式访问的所有内容都有助于提高性能。
要概括的不仅仅是不可能,但“参考地点”是一个很好的目标。
答案 3 :(得分:1)
由于内存的布局方式,第一个版本维护数据局部性,因此导致缓存未命中次数减少。
答案 4 :(得分:1)
内存分配只发生一次,并且它在开头,所以它不是原因。原因是运行时如何计算地址。在这两种情况下,存储器地址都计算为
(i * (IMGY * IMGX)) + (j * IMGX) + 0
在第一个算法中
(i * (IMGY * IMGX)) gets calculates 8192 times
(j * IMGX) gets calculated 8192 * 8192 times
在第二个算法中
(i * (IMGY * IMGX)) gets calculates 8192 * 8192 times
(j * IMGX) gets calculated 8192 times
自
(i * (IMGY * IMGX))
涉及两次乘法,这样做需要花费更多时间。这就是原因
答案 5 :(得分:0)
是的,它与内存分配有关。第一个循环索引img
的内部维度,每次恰好只跨越3个字节。这很容易在一个内存页面内(我相信这里的常见大小为4kB)。但是对于第二个版本,外部维度的索引变化很快。这将导致内存读取扩展到更大范围的内存 - 即sizeof (char[IMGX][3])
字节,即24kB。随着内部指数的每次变化,这些跳跃开始再次发生。这将打到不同的页面,可能有点慢。我也听说CPU读取内存。这将使第一个版本受益,因为在它读取时,该数据可能已经在缓存中。我可以想象第二个版本没有从中受益,因为它会使来回内存大量跳跃。
我怀疑差异不是那么大,但如果算法运行多次,它最终会变得明显。您可能想阅读维基百科上的文章Row-major Order
。这是用于在C中存储多维数组的方案。