【问题标题】:2D array access time comparison二维数组访问时间比较
【发布时间】:2014-03-03 18:47:36
【问题描述】:

我有两种构造二维数组的方法:

int arr[NUM_ROWS][NUM_COLS];
//...
tmp = arr[i][j]

扁平化数组

int arr[NUM_ROWS*NUM_COLS];
//...
tmp = arr[i*NuM_COLS+j];

我正在处理图像,因此即使是访问时间的一点改进也是必要的。哪个更快?我在考虑第一个,因为第二个需要计算,但是第一个需要两个寻址,所以我不确定。

【问题讨论】:

  • 这没什么区别 - 在性能方面还有更重要的考虑因素,例如访问模式、步幅等。
  • 一般情况下,编译器已经预先计算了所需的空间,代码调用了预先计算好的大小的分配函数。预先计算没有额外的惩罚。
  • 如果要提高性能: 1) 使用在程序启动时分配的固定大小的数组。 2) 在网上搜索“数据缓存优化”以获取有关如何分配和使用 2D 数组以获得处理器数据缓存的最大性能的建议。
  • 对于图像处理,您不太可能使用静态分配的数组进行图像处理。在这种情况下,您的问题取决于您如何分配动态二维数组。看我的回答。
  • @PaulR,因为这是用于图像处理,我假设 OP 需要动态数组。在这种情况下,如何分配 2D 动态数组确实会有所不同。

标签: c++ c performance


【解决方案1】:

我认为没有任何性能差异。在这两种情况下,系统将分配相同数量的连续内存。对于计算i*Numcols+j,您可以为一维数组声明执行此操作,或者系统在二维情况下执行此操作。唯一关心的是易用性。

【讨论】:

  • 请注意,这不适用于动态的真正二维数组。
  • 是的,但是要求 OP 进行静态分配,这是在运行时之前分配的。
  • 当然,只是要注意:)
  • @lisyarus,您能否详细解释一下“这不适用于动态真正二维数组”的意思?
  • @Zboson:如果您创建一个二维数组为int **a = new int* [NUM_ROWS]; 等等,编译器将无法将其优化为线性数组。我知道,这很明显,只是一个注释。
【解决方案2】:

您应该相信您的编译器在优化标准代码方面的能力。

您还应该相信具有快速数字乘法指令的现代 CPU。

不要费心使用一个或另一个!

我 - 几十年前 - 通过使用指针而不是使用 2d-array-calculation 极大地优化了一些代码 --> 但这 a) 只有在它是存储指针的选项时才有用 - 例如在一个循环中和b)影响很小,因为我猜现代cpus应该在一个周期内进行二维数组访问?值得衡量!可能与数组大小有关。

在任何情况下,如果适用的话,使用 ptr++ 或 ptr += NuM_COLS 的指针肯定会快一点!

【讨论】:

    【解决方案3】:

    第一种方法几乎总是更快。一般来说(因为总是存在极端情况)处理器和内存架构以及编译器可能具有内置的优化功能,以帮助二维数组或其他类似的数据结构。例如,GPU 针对矩阵(二维数组)数学进行了优化。

    所以,总的来说,如果可能的话,我会允许编译器和硬件优化你的内存和地址算法。

    ...我也同意@Paul R,在性能方面,比您的数组分配和地址算术要考虑的要多得多。

    【讨论】:

    • 你能看一下汇编语言吗?我确信这两种情况都是对具有硬编码常量值的内存分配函数的一次调用。
    • 以数组大小为 2 的幂的情况为例,例如 16 或 32 行乘 8 或 64 列。编译器可能会用位移代替乘法来获得元素地址。如果你对计算进行硬编码,那么在这种情况下你肯定会变慢。同样,我认为执行您自己的地址计算没有任何好处,除非有特殊需要。
    • 如果数组大小是固定大小,则编译器在发出代码之前已经进行了大小计算。对于在运行时确定的数组大小,编译器必须发出代码来执行计算。两种截然不同的表现。
    • 这里遗漏了一个乘法,即 sizeof(int) * NUM_ROWS * NUM_COLS。不能在运行时优化吗?
    • 如果 NUM_ROWS 和 NUM_COLS 是常量,会在编译时计算。您需要在运行时计算常量吗?
    【解决方案4】:

    有两种情况需要考虑:编译时定义和运行时定义数组大小。性能有很大差异。

    静态分配、全局或文件范围、固定大小的数组:
    编译器知道数组的大小并告诉链接器在数据/内存部分分配空间。这是最快的方法。

    例子:

    #define ROWS 5
    #define COLUMNS 6
    int array[ROWS][COLUMNS];
    int buffer[ROWS * COLUMNS];
    

    运行时分配、函数局部作用域、固定大小数组:
    编译器知道数组的大小,并告诉代码在本地内存(也称为堆栈)中为数组分配空间。通常,这意味着向堆栈寄存器添加一个值。通常是一两个指令。

    例子:

    void my_function(void)
    {
      unsigned short my_array[ROWS][COLUMNS];
      unsigned short buffer[ROWS * COLUMNS];
    }
    

    运行时分配、动态内存、固定大小数组:
    同样,编译器已经计算了数组所需的内存量,因为它被声明为固定大小。编译器发出代码以调用所需数量的内存分配函数(通常作为参数传递)。由于函数调用和查找一些动态内存(可能还有垃圾收集)所需的开销,所以速度会慢一些。

    例子:

    void another_function(void)
    {
      unsigned char * array = new char [ROWS * COLS];
      //...
      delete[] array;
    }
    

    运行时分配、动态内存、可变大小:
    无论数组的维度如何,编译器都必须发出代码来计算要分配的内存量。然后将该数量传递给内存分配函数。由于计算大小所需的代码,比上面慢一点。

    例子:

    int * create_board(unsigned int rows, unsigned int columns)
    {
      int * board = new int [rows * cols];
      return board;
    }
    

    【讨论】:

      【解决方案5】:

      由于您的目标是图像处理,所以我假设您的图像对于静态数组来说太大了。关于动态分配数组的正确问题

      在 C/C++ 中,您可以通过多种方式分配动态二维数组 How do I work with dynamic multi-dimensional arrays in C?。为了在 C/C++ 中实现这一点,我们可以使用 malloc 和强制转换(对于 C++ 只有你可以使用 new)

      方法一:

      int** arr1 = (int**)malloc(NUM_ROWS * sizeof(int*));
      for(int i=0; i<NUM_ROWS; i++)
          arr[i] = (int*)malloc(NUM_COLS * sizeof(int));
      

      方法二:

      int** arr2 = (int**)malloc(NUM_ROWS * sizeof(int*));
      int* arrflat = (int*)malloc(NUM_ROWS * NUM_COLS * sizeof(int));
      for (int i = 0; i < dimension1_max; i++)
        arr2[i] = arrflat + (i*NUM_COLS);
      

      方法 2 本质上创建了一个连续的二维数组:即arrflat[NUM_COLS*i+j]arr2[i][j] 应该具有相同的性能。但是,不应期望方法 1 中的 arrflat[NUM_COLS*i+j]arr[i][j] 具有相同的性能,因为 arr1 不连续。然而,方法 1 似乎是最常用于动态数组的方法。

      一般情况下,我使用arrflat[NUM_COLS*i+j],所以我不必考虑如何分配动态二维数组。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-10
        • 1970-01-01
        • 2017-04-06
        • 1970-01-01
        • 2021-11-24
        • 2013-05-19
        相关资源
        最近更新 更多