【问题标题】:Most efficient way to get columns of a multi dimensional array in C在C中获取多维数组列的最有效方法
【发布时间】:2010-07-14 08:34:08
【问题描述】:

我正在尝试在 C 中创建一个矩阵数据结构。我有一个结构,并且有一个二维 void 指针数组(大小在堆中动态定义)用于该结构中的货物部分(数据)。

给定一个列索引,我想在一维数组中获取该列的值。使用一个 for 或 while 循环很容易做到这一点。但是如果这个矩阵的行数是 N,那么得到一个列向量需要 O(N) 时间。我可以通过 memcpy 等内存操作更有效地做到这一点吗?否则我该如何提高性能(我的数据非常结构化,我需要将其存储在某种矩阵中)。

【问题讨论】:

    标签: c performance matrix


    【解决方案1】:

    如果一列中的行数为 N,则您无法在少于 O(N) 的时间内复制、读取或以其他方式操作整列。这是一个坚定的下限;每个元素都必须考虑,有N个。

    所以不,你不能让它比 O(N) 更快。

    请注意,对于已知大小的二维数组,编译器会将 x[3][5] 转换为 x+((3*num_cols)+5)*size_of_element。因此,使您的数组更快的一种方法是删除其动态大小。

    另一个重要的一点是,对内存的顺序访问并不总是最快的——所以仅仅旋转你的数组九十度不一定会给你最好的结果。将阻塞视为一种优化技术。底线:最佳内存布局取决于您的访问模式和硬件参数,例如缓存行长度和缓存大小。

    【讨论】:

      【解决方案2】:

      如果您想复制矩阵中的数据,无论是行还是列,都不能在少于 O(N) 的时间内完成,除非是小 N,硬件功能可能会有所帮助。

      但是,如果您的矩阵是不可变的,您可以使用烟雾和镜子来产生具有单独列向量的错觉。

      下面的代码直接输入到答案文本框中,甚至还没有编译。使用风险自负!

      您的矩阵类型被定义为结构体:

      typedef struct 
      {
          unsigned int refCount;  // how many Matrixes are referencing this data ref
          size_t lineWidth;       // number of doubles between element at row = n, col = 0 and row = n +1, col = 0 
          double* data;           // the actual data
      } DataRef;
      
      typedef struct
      {
          size_t rows;            // num rows in matrix
          size_t cols;            // num cols in matrix
          size_t dataOffset;      // offset in doubles from the start of data of element at row = 0, col = 0
          DataRef* data;
      } Matrix;
      

      创建一个全新的矩阵(我省略了所有错误处理以使其更简单)。

      Matrix* matrix_create(size_t rows, size_t cols, const double* values)
      {
          Matrix* ret = calloc(1, sizeof *ret);
          ret->rows = rows;
          ret->cols = cols;
          ret->dataOffset = 0;
          ret->data = calloc(1, sizeof *dataRef);
          ret->data->lineWidth = cols;
          ret->data->data = allocateAndCopy(rows * cols, values); // mallocs a new block of doubles big enough for the values
          ret->data->refCount = 1;
          return ret;
      }
      

      访问一个元素(同样没有错误处理,例如边界错误)

      double matrix_elementAt(Matrix* matrix, size_t row, size_t col)
      {
          size_t offset = matrix->dataOffset + row * matrix->data->lineWidth + col;
          return *(matrix->data->data + offset);
      }
      

      从另一个矩阵的矩形区域创建一个新矩阵(同样,需要错误处理)

      Matrix* matrix_createFromRegion(Matrix* old, size_t startRow, size_t startCol, size_t rows, size_t cols)
      {
          Matrix* ret = calloc(1, sizeof *ret);
          ret->rows = rows;
          ret->cols = cols;
          ret->dataOffset = old->dataOffset + startRow * old->dataLineWidth + startCol;
          ret->data = old->data;
          ret->data->refCount++;
          return ret;
      }
      

      从另一个矩阵中的列创建一个新矩阵:

      Matrix* vector = matrix_createFromRegion(aMatrix, 0, colYouWant, matrix_numRows(aMatrix), 1);
      

      释放矩阵

      void matrix_free(Matrix* aMatrix)
      {
          if (aMatrix->data->refCount == 1)
          {
              free(aMatrix->data->data);
              free(aMatrix->data);
          }
          else
          {
              aMatrix->data->refCount--;
          }
          free(aMatrix);
      }
      

      如果你想要可变矩阵,任何时候你修改一个元素,检查 refCount,如果它大于 1,在修改它之前复制 DataRef(减少旧 dataRef 上的 refCount),否则修改 dataRef。

      现在上面使用了大量的 malloc,因此可能比小矩阵的简单实现效率低。但是,您可以维护未使用的 DataRef 结构和 Matrix 结构的列表,而不是在完成后释放它们,而是将它们放在空闲列表中。分配新结构时,从空闲列表中获取结构,除非它们为空。这样,获取表示现有矩阵的一列的矩阵通常需要固定时间。

      【讨论】:

      • 谢谢,我认为这是这里最好的解决方案,实际上我的矩阵非常大,所以它不适合 5 GB RAM :D。
      【解决方案3】:

      正如 Borealid 所说,您无法改进 O(N)。但是,如果您重新排序数据以使行是列,列是行,则可以加快复制操作。这将允许您使用 memcpy 复制数据。

      【讨论】:

      • 是的 JeremyP 是对的 memcpy 也是 O(N):google.com/codesearch/p?hl=en#lIRf952n7hs/libc/memcpy.c
      • 同意,但它可能比遍历数组更有效......可能;-)
      • 我怀疑需要复制行和列,所以转置矩阵只是将问题转移到行..
      • 可能,但OP没有提及,所以我认为值得指出。
      【解决方案4】:

      我的解决方案:

      1. 不要使用多维数组。它们在 C99 之前是不灵活的(不能改变所有维度),并且无法进行如下高效操作。相反,只需使用一维数组并自己进行元素索引算法。

      2. 现在,您可以设置一个指针 src 指向列的第一个元素 (src = &matrix[row*ncols+col];),然后复制该列:for (i=0; i<nrows; i++, src+=ncols) dest[i] = *src;

      【讨论】:

      • 嗯,实际上我熟悉将一维数组从 cuda 映射到多维,但我不确定这是否会产生显着的性能差异。
      • 除非您的编译器可以将乘法排除在循环之外,否则它应该会产生巨大的影响。
      猜你喜欢
      • 1970-01-01
      • 2013-02-13
      • 1970-01-01
      • 2013-11-07
      • 2015-07-05
      • 2012-02-19
      • 2014-04-11
      • 1970-01-01
      • 2019-07-31
      相关资源
      最近更新 更多