【问题标题】:Why would 2D array access be faster than 1D array access?为什么二维数组访问比一维数组访问更快?
【发布时间】:2017-04-06 11:22:34
【问题描述】:

我有两个程序,用 g++ 编译并在 linux 上执行。程序 1 创建一个二维数组,然后测量访问其所有元素 100000 次需要多长时间:

#include <time.h>
#include <iostream>

int main()
{
  clock_t time;
  int i, y, x;

  int matrix[9][9]{{ 0,  1,  2,  3,  4,  5,  6,  7,  8},
                   { 9, 10, 11, 12, 13, 14, 15, 16, 17},
                   {18, 19, 20, 21, 22, 23, 24, 25, 26},
                   {27, 28, 29, 30, 31, 32, 33, 34, 35},
                   {36, 37, 38, 39, 40, 41, 42, 43, 44},
                   {45, 46, 47, 48, 49, 50, 51, 52, 53},
                   {54, 55, 56, 57, 58, 59, 60, 61, 62},
                   {63, 64, 65, 66, 67, 68, 69, 70, 71},
                   {72, 73, 74, 75, 76, 77, 78, 79, 80}};

  time = clock();

  for (i = 0; i < 100000; i++)
  {
    for (x = 0; x < 9; x++)
    {
      for (y = 0; y < 9; y++)
      {
        matrix[x][y];
      }
    }
  }

  time = clock() - time;
  std::cout << "Clicks:     " << time << std::endl;
  std::cout << "Time taken: " << (double) time / CLOCKS_PER_SEC << "s" << std::endl;
}

程序 2 创建一个一维数组,并测量访问其所有元素 100000 次所需的时间:

#include <time.h>
#include <iostream>

int main()
{
  clock_t time;
  int i, j;

  int vector[81] = { 0,  1,  2,  3,  4,  5,  6,  7,  8,
                     9, 10, 11, 12, 13, 14, 15, 16, 17,
                    18, 19, 20, 21, 22, 23, 24, 25, 26,
                    27, 28, 29, 30, 31, 32, 33, 34, 35,
                    36, 37, 38, 39, 40, 41, 42, 43, 44,
                    45, 46, 47, 48, 49, 50, 51, 52, 53,
                    54, 55, 56, 57, 58, 59, 60, 61, 62,
                    63, 64, 65, 66, 67, 68, 69, 70, 71,
                    72, 73, 74, 75, 76, 77, 78, 79, 80};

  time = clock();

  for (i = 0; i < 100000; i++)
  {
    for (j = 0; j < 81; j++)
    {
      vector[j];
    }
  }

  time = clock() - time;
  std::cout << "Clicks:     " << time << std::endl;
  std::cout << "Time taken: " << (double) time / CLOCKS_PER_SEC << "s" << std::endl;
}

执行程序 1 后,我的输出是:

Clicks:     8106
Time taken: 0.008106s

执行程序 2 后,我的输出是:

Clicks:     15958
Time taken: 0.015958s

据我了解,一维数组存储在连续的内存块中。同样,静态二维数组的行存储在连续的内存块中。相反,动态二维数组的行可能不会存储在连续的内存块中。如果这是真的,那么程序 2 的速度应该至少与程序 1 相似,因此我的问题是为什么程序 1 会比程序 2 快得多?

【问题讨论】:

  • 如果我错了,请纠正我,但 c 对待 2d 和 1d 数组的处理方式不一样吗?
  • 你在什么编译器和操作系统上测试过它?
  • @KyleKW 我相信是这样,但索引会有所不同,即在二维数组中,您使用索引 = 宽度 * 列索引 + 行索引访问每个元素。我关心的是为什么二维数组的访问速度比一维数组快。
  • 按照我所说的,你可能想看看这个answer(第2和第3段是关于缓存和prefetching
  • 试图测量如此短时间的性能通常会由于其他进程引起的噪音而产生偏差 - 调度程序管理 10-20 毫秒级别的上下文切换,因此您需要至少一个量级的测试更慢,因为它没有效果。将迭代次数增加到10000000 后,我得到了相反的结果:4.69s 用于 2D 数组,3.44s 用于 1D 数组,两种情况下的测量误差均小于 1%。

标签: c++ arrays performance matrix vector


【解决方案1】:

编译器可能会删除循环(某种优化),因为

  1. 实际上你在循环中什么也没做。

  2. 可以将矩阵视为 const 数组。

  3. 程序 1 比程序 2 快。( :

要查看您的代码在编译过程中是否发生删除,您可以将最外层循环增加 100 倍,并查看执行所需的时间是否显着增加(不一定是精确的 100 倍)。

如果为真,您可以通过循环执行一些实际工作来防止这种优化(计算总和,然后不要忘记打印它)并向您的矩阵引入一些“不可预测的”更改,例如:

srand(10);
for (int i=0; i<9; ++i) {
  matrix[i][i] = rand()%100;
}

此外,编译器可能会对您的代码进行一些其他优化,例如,扩展您的循环,甚至是您正在访问的元素的地址(它们不再在运行时计算),您可以通过使循环的执行时间“不可预测”:

#include <chrono>
#include <iostream>
#include <cstdlib>

int array[100];
int array2[10][10];

int64_t Sum1D(int len) {
  int64_t sum = 0;
  for (int i=0; i<100000; ++i) {
    for (int j=0; j<len; ++j) {
        sum += array[j];
    }
  }
  return sum;
}

int64_t Sum2D(int len1, int len2) {
  int64_t sum = 0;
  for (int i=0; i<100000; ++i) {
    for (int j=0; j<len1; ++j) {
      for (int k=0; k<len2; ++k)
        sum += array2[j][k];
    }
  }
  return sum;
}

int main()
{
  for (int i=0; i<100; ++i) {
    array[i] = rand();
    array2[i%10][i/10] = rand();
  }

  auto time = std::chrono::steady_clock::now();

  //int64_t sum = Sum1D(100);
  int64_t sum = Sum2D(10,10);

  auto duration = std::chrono::steady_clock::now()-time;
  std::cout << sum << "!" << duration.count() << std::endl;

  return 0;
} 

这最终使程序 1 比程序 2 慢。 ( :> )

【讨论】:

    【解决方案2】:

    这是我发现的:

    1. 如果你真的使用该值,那么运行时间几乎相同,例如,将matrix[x][y]; 更改为matrix[x][y] += 1; 并将vector[j]; 更改为vector[j] += 1;

      >     Clicks:     28519
      >     Time taken: 0.028519s
      

      >     Clicks:     29941
      >     Time taken: 0.029941s
      
    2. 如果不进行上述更改,请在编译时进行优化,g++ -O3 &lt;filename&gt;.cpp,这会在同一时间得到两个程序相同的以下输出:

      $./a.out

      >     Clicks:     2
      >     Time taken: 2e-06s
      

    所以,您要指出的是编译器优化。

    【讨论】:

    • 不知道为什么对你投反对票,但我现在反驳了。不过,我会用你的结论来引出你的答案,因为这些问题经常出现,而且在 99% 的情况下,这是因为在限制一组数据的过程中得出结论。
    • @michaelDorgan 谢谢 :)
    猜你喜欢
    • 2014-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-11
    • 2012-06-01
    • 2013-06-29
    • 2012-06-01
    • 1970-01-01
    相关资源
    最近更新 更多