提问人:Jared Frazier 提问时间:12/26/2022 最后编辑:Jared Frazier 更新时间:1/1/2023 访问量:166
了解 2D 阵列的高效连续内存分配
Understanding efficient contiguous memory allocation for a 2D array
问:
以下代码来自《并行和高性能计算》的第 93 页,是 2D 数组的单个连续内存分配:
double **malloc_2D(int nrows, int ncols) {
double **x = (double **)malloc(
nrows*sizeof(double*)
+ nrows*ncols*sizeof(double)); // L1
x[0] = (double *)x + nrows; // L2
for (int j = 1; j < nrows; j++) { // L3
x[j] = x[j-1] + ncols;
}
return x;
}
该书指出,这提高了内存分配和缓存效率。有什么理由在效率上更喜欢第一个代码而不是下面的代码?下面的代码似乎更具可读性,并且也很容易与 MPI 一起使用(我之所以提到这一点,是因为本书稍后还介绍了 MPI)。
double *malloc_2D(int nrows, int ncols) {
double *M = (double *)malloc(nrows * ncols * sizeof(double))
return M
}
我包含下图以确保我的第一个代码的心智模型是正确的。如果不是,请在答案中提及。该图像是调用第一个函数以创建 5 x 2 矩阵的结果。请注意,为了清楚起见,我只是在下图的框中写下索引,当然存储在这些内存位置的值不会是 0 到 14。另请注意,L# 引用第一个代码中的行。
答:
该书指出,这提高了内存分配和缓存效率。
与经常看到的单独分配指针的方法相比,本书的代码提高了效率,如下所示:
double **x = malloc(nrows * sizeof *x);
for (size_t i = 0; i < nrows; ++i)
x[i] = malloc(ncols * sizeof *x[i]);
(请注意,所有方法都应测试结果并处理分配失败。此处的讨论省略了这一点。malloc
该方法单独分配每一行(从其他行和指针)。本书的方法有一些好处,即只进行一次分配,并且数组的内存是连续的。此外,不同行中元素之间的关系是已知的,这可能允许程序员在设计适用于缓存和内存访问的算法时利用这些关系。
有什么理由在效率上更喜欢第一个代码而不是下面的代码?
不是为了效率,不。本书的方法和上面的方法都有一个缺点,即它们通常需要对每个数组访问进行指针查找(除了基指针,)。在处理器可以从行的内存中获取元素之前,它必须从内存中获取行的地址。x
使用您显示的方法,不需要进行此额外的查找。此外,处理器和/或编译器可能能够预测有关访问的某些事情。例如,使用您的方法,编译器可能能够看到 是与 不同的元素,而 with 和 ,它通常无法知道这两个指针并且是不同的。M[(i+1)*ncols + j]
M[(i+2)*cols + j]
x[i+1][j]
x[i+2][j]
x[i+1]
x[i+2]
这本书的代码也有缺陷。它分配的字节数为 。假设 r 是 , c 是 , p 是 和 d 是 。然后代码分配 rp + rcd 字节。然后,代码设置为 。因为它强制转换为 ,所以加法是以指向类型 的单位完成的。因此,这会将 rd 字节添加到起始地址。之后,它期望拥有数组的所有元素,即 rcd 字节。因此,即使代码分配了 rp + rcd,代码也使用 rd + rcd 字节。 如果 p > d,则数组末尾的某些元素将位于分配的内存之外。在当前的普通 C 实现中,大小 小于或等于 的大小,但不应依赖此。它应该计算 ,而不是设置为 ,加上类型元素的大小加上足够的填充,以达到 的对齐要求,并且它应该在分配中包含该填充。nrows*sizeof(double*) + nrows*ncols*sizeof(double)
nrows
ncols
sizeof(double*)
sizeof(double)
x[0]
(double *)x + nrows
double *
nrows
double
double *
double
x[0]
(double *)x + nrows;
x
nrows
double *
double
如果我们不能使用可变长度数组,那么数组索引可以由宏提供,例如定义一个用 替换的宏,例如 。x(i, j)
x[i*ncols+j]
#define x(i, j) x[(i)*ncols + (j)]
评论
x(i,j)
x[i*ncols+j]
size_t ix(size_t i, size_t j, size_t ncols) { return i*ncols+j; }
x[ix(i, j, ncols)]
评论
arr[i][j]
arr[i * nrows + j]
arr[i * nrows +j]
i*nrows+j
arr[i][j]
double (*arr)[ncols] = calloc(nrows, sizeof *arr);