c - 为什么循环顺序会影响二维数组迭代时的性能?

标签 c performance for-loop optimization cpu-cache

下面是两个几乎相同的程序,只是我交换了 ij 变量。它们运行的​​时间不同。有人能解释一下为什么会发生这种情况吗?

版本 1

#include <stdio.h>
#include <stdlib.h>

main () {
  int i,j;
  static int x[4000][4000];
  for (i = 0; i < 4000; i++) {
    for (j = 0; j < 4000; j++) {
      x[j][i] = i + j; }
  }
}

版本 2

#include <stdio.h>
#include <stdlib.h>

main () {
  int i,j;
  static int x[4000][4000];
  for (j = 0; j < 4000; j++) {
     for (i = 0; i < 4000; i++) {
       x[j][i] = i + j; }
   }
}

最佳答案

正如其他人所说,问题在于数组中内存位置的存储:x[i][j]。下面是一些关于原因的见解:

你有一个二维数组,但计算机中的内存本质上是一维的。所以当你想象你的数组是这样的:

0,0 | 0,1 | 0,2 | 0,3
----+-----+-----+----
1,0 | 1,1 | 1,2 | 1,3
----+-----+-----+----
2,0 | 2,1 | 2,2 | 2,3

您的计算机将其作为一行存储在内存中:

0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | 1,2 | 1,3 | 2,0 | 2,1 | 2,2 | 2,3

在第二个示例中,您通过首先循环第二个数字来访问数组,即:

x[0][0] 
        x[0][1]
                x[0][2]
                        x[0][3]
                                x[1][0] etc...

这意味着您正在按顺序击中它们。现在看看第一个版本。你正在做:

x[0][0]
                                x[1][0]
                                                                x[2][0]
        x[0][1]
                                        x[1][1] etc...

由于 C 在内存中布置二维数组的方式,您要求它到处跳转。但现在问题来了:为什么这很重要?所有内存访问都是相同的,对吗?

否:因为缓存。内存中的数据以小块(称为“缓存行”)的形式传送到 CPU,通常为 64 字节。如果您有 4 字节整数,则意味着您将在一个整齐的小包中获得 16 个连续的整数。实际上,获取这些内存块的速度相当慢;您的 CPU 可以在加载单个缓存行的时间内完成大量工作。

现在回顾一下访问顺序:第二个示例是(1)抓取 16 个整数的 block ,(2)修改所有这些,(3)重复 4000*4000/16 次。这很好,而且速度很快,而且 CPU 总是有工作要做。

第一个示例是 (1) 抓取 16 个整数的 block ,(2) 仅修改其中一个,(3) 重复 4000*4000 次。这将需要 16 倍的内存“读取”次数。实际上,您的 CPU 必须花时间等待内存出现,而当内存出现时,您就在浪费宝贵的时间。

重要说明:

现在您已经有了答案,这里有一个有趣的注释:您的第二个示例没有必然是最快的示例。例如,在 Fortran 中,第一个示例很快,第二个示例很慢。这是因为 Fortran 不像 C 那样将事物扩展为概念上的“行”,而是扩展为“列”,即:

0,0 | 1,0 | 2,0 | 0,1 | 1,1 | 2,1 | 0,2 | 1,2 | 2,2 | 0,3 | 1,3 | 2,3

C 的布局称为“行优先”,Fortran 的布局称为“列优先”。正如您所看到的,了解您的编程语言是行优先还是列优先非常重要!以下是更多信息的链接:http://en.wikipedia.org/wiki/Row-major_order

关于c - 为什么循环顺序会影响二维数组迭代时的性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26048706/

相关文章:

c# - 检查 C# 代码性能的最佳方法

MySQL:ORDER BY 或 GROUP BY 慢?

java - 如何使用递归方法打印 a 到 z

java - Head First Java Mix For 5 练习 - 中断子句

c - 如何从另一个模块访问静态变量(模块范围内的静态变量)?在 C 中

c++ - 我如何知道 IMAGE_THUNK_DATA 数组何时终止?

c - 从 C 声明生成二进制接口(interface)规范

c++ - 如何使用 OpenCV 检测视频中的方 block ?

c - 使用并行算法进行求和减少 - 与 CPU 版本相比性能较差

for-loop - Windows批处理中以逗号分隔的for循环列表