为什么迭代2D数组行主要比列主要更快?

时间:2015-11-15 17:17:49

标签: c++ arrays compiler-construction iteration

这是一个简单的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更快?

3 个答案:

答案 0 :(得分:14)

这显然取决于你所使用的机器,但非常一般地说:

  1. 您的计算机将部分程序的内存存储在缓存中,该缓存的延迟比主内存小得多(即使在补偿缓存命中时间时)。

  2. C数组按行主要顺序存储。这意味着如果您要求元素x,则元素x+1将存储在主存储器中紧邻存储x的位置。

  3. 您的计算机缓存的典型特征是先发制人地&#34;使用尚未使用的内存地址填充缓存,但是本地已接近程序已使用的内存。想想你的电脑说:&#34;好吧,你想要地址X的记忆,所以我假设你很快会想要X + 1的记忆,因此我会先发制人地抓住你并将它放入你的缓存&#34;。

  4. 当您通过行主要顺序枚举数组时,您将以一种以连续方式存储在内存中的方式枚举它,并且您的机器已经自由地预先加载那些地址进入缓存,因为它猜到你想要它。因此,您可以获得更高的缓存命中率。当您以另一种非连续方式枚举数组时,您的计算机可能无法预测您正在应用的内存访问模式,因此它无法预先将内存地址提取到缓存中对你而言,你不会发生多少缓存命中,因此必须更频繁地访问主内存,这比你的缓存慢。

    此外,这可能更适合https://cs.stackexchange.com/,因为系统缓存的行为方式是在硬件中实现的,空间位置问题似乎更适合那里。

答案 1 :(得分:6)

你的数组实际上是ragged array,因此行主要不完全是一个因素。

你会看到更好的性能迭代列然后行,因为行内存是线性布局的,顺序读取很容易让缓存预测器预测,并且你将指针解引用分摊到第二维,因为它只需要每行做一次。

当您遍历行然后遍历列时,每次迭代都会引发指向第二维的指针。因此,通过遍历行,您将添加指针取消引用。除了内在成本之外,它对缓存预测也是不利的。

如果你想要一个真正的二维数组,使用行主要排序在内存中,你会想要......

int A[1000][1000];

这以行主顺序连续排列内存,而不是一个指向数组的指针数组(它们没有连续排列)。使用row-major对此数组进行迭代仍然比迭代column-major执行速度更快,因为空间局部性和缓存预测。

答案 2 :(得分:2)

简短的回答是CPU缓存。 Scott Mayers非常清楚地解释了here