这是一个简单的C ++代码,用于比较迭代的2D数组行major和列major。
#include <iostream>
#include <ctime>
using namespace std;
const int d = 10000;
int** A = new int* [d];
int main(int argc, const char * argv[]) {
for(int i = 0; i < d; ++i)
A[i] = new int [d];
clock_t ColMajor = clock();
for(int b = 0; b < d; ++b)
for(int a = 0; a < d; ++a)
A[a][b]++;
double col = static_cast<double>(clock() - ColMajor) / CLOCKS_PER_SEC;
clock_t RowMajor = clock();
for(int a = 0; a < d; ++a)
for(int b = 0; b < d; ++b)
A[a][b]++;
double row = static_cast<double>(clock() - RowMajor) / CLOCKS_PER_SEC;
cout << "Row Major : " << row;
cout << "\nColumn Major : " << col;
return 0;
}
d 的不同值的结果:
d = 10 ^ 3 :
行专业:0.002431
专栏:0.017186
d = 10 ^ 4 :
行专业:0.237995
专栏:2.04471
d = 10 ^ 5
行专业:53.9561
专栏:444.339
现在问题是为什么row major比列major更快?
答案 0 :(得分:14)
这显然取决于你所使用的机器,但非常一般地说:
您的计算机将部分程序的内存存储在缓存中,该缓存的延迟比主内存小得多(即使在补偿缓存命中时间时)。
C数组按行主要顺序存储。这意味着如果您要求元素x
,则元素x+1
将存储在主存储器中紧邻存储x
的位置。
您的计算机缓存的典型特征是先发制人地&#34;使用尚未使用的内存地址填充缓存,但是本地已接近程序已使用的内存。想想你的电脑说:&#34;好吧,你想要地址X的记忆,所以我假设你很快会想要X + 1的记忆,因此我会先发制人地抓住你并将它放入你的缓存&#34;。
当您通过行主要顺序枚举数组时,您将以一种以连续方式存储在内存中的方式枚举它,并且您的机器已经自由地预先加载那些地址进入缓存,因为它猜到你想要它。因此,您可以获得更高的缓存命中率。当您以另一种非连续方式枚举数组时,您的计算机可能无法预测您正在应用的内存访问模式,因此它无法预先将内存地址提取到缓存中对你而言,你不会发生多少缓存命中,因此必须更频繁地访问主内存,这比你的缓存慢。
此外,这可能更适合https://cs.stackexchange.com/,因为系统缓存的行为方式是在硬件中实现的,空间位置问题似乎更适合那里。
答案 1 :(得分:6)
你的数组实际上是ragged array,因此行主要不完全是一个因素。
你会看到更好的性能迭代列然后行,因为行内存是线性布局的,顺序读取很容易让缓存预测器预测,并且你将指针解引用分摊到第二维,因为它只需要每行做一次。
当您遍历行然后遍历列时,每次迭代都会引发指向第二维的指针。因此,通过遍历行,您将添加指针取消引用。除了内在成本之外,它对缓存预测也是不利的。
如果你想要一个真正的二维数组,使用行主要排序在内存中,你会想要......
int A[1000][1000];
这以行主顺序连续排列内存,而不是一个指向数组的指针数组(它们没有连续排列)。使用row-major对此数组进行迭代仍然比迭代column-major执行速度更快,因为空间局部性和缓存预测。
答案 2 :(得分:2)
简短的回答是CPU缓存。 Scott Mayers非常清楚地解释了here