我想将2D方阵的第一行移到最后一行。所以如果我有像A这样的矩阵,我想得到B。
我可以使用两个简单的for循环来完成此操作。 E.g。
void shift(int M, int N, int A[M][N]){
int i, j,temp;
for (i = 1; i < M; i++){
for (j = 0; j < N; j++){
temp=A[i][j];
A[i][j]=A[i-1][j];
A[i-1][j]=temp;
}
}
}
但我希望尽可能减少缓存未命中数。有关如何做到这一点的任何提示?
答案 0 :(得分:2)
/* M is the number of rows; N is the number of columns. */
void matrix_shift(int M, int N, int A[M][N]) {
size_t rowbytes = N * sizeof(int);
int temprow[N];
memcpy(temprow, A, rowbytes); // store first row
memmove(A, A + 1, (M-1) * rowbytes); // shift up
memcpy(A + (M-1), temprow, rowbytes); // replace last row
}
这使它变得简单并且依赖于应该在任何通用平台上高度优化的例程。复制了一个额外的行,但在方阵矩阵的情况下,这是一个次要的低效率。
答案 1 :(得分:1)
我刚看到你对4x4矩阵的评论。一个4x4 int
数组适合单个缓存行(在现代x86 CPU上,缓存行为64B)。在这种情况下,您希望编译器生成类似
## matrix address in [rdi]
movups xmm0, [rdi]
movups xmm1, [rdi+16]
movups xmm2, [rdi+32]
movups xmm3, [rdi+48]
movups [rdi], xmm1 ; doing all the stores after all the loads avoids any possible false dependency
movups [rdi+16], xmm2
movups [rdi+32], xmm3
movups [rdi+48], xmm0
或者可能更少的AVX 256b加载/存储,但未对齐的AVX可能会更糟。如果阵列是64B对齐的,那么所有加载/存储都不会跨越缓存线边界。因此,2 vmovups ymm
个加载,一个vmovups ymm
商店,一个vmovups xmm
商店(到最后)和一个vextractf128
商店(到开头)。
如果你很幸运,当函数内联到具有编译时常量值4
的调用者时,John的memcpy会优化到类似的东西。
对于小型阵列,问题不在于缓存未命中,而是如何以尽可能少的开销实现整个副本。我在下面提到的关于引入间接级别的想法并不是一个好主意,因为加载所有数据并将其存储回来真的很便宜。
如果你在矩阵的末尾留出另一行的空间,你可以将第一行复制到这个额外的空间,并将指针传递给第二行。
这使您可以暂时拥有矩阵的不同视图,但这不是一个可重复的过程。
如果你有一个大缓冲区,你可以继续以这种方式旋转矩阵行,直到你到达保留空间的末尾并且必须将数组复制回缓冲区的顶部。这最大限度地减少了复制开销,但确实意味着您正在触摸一些新内存。
如果行复制开销很大,引入间接级别可能是个好主意。根据在对行进行洗牌后使用它的代码的访问模式,这可能会更糟。这可能是指向行指针数组的用例,而不是普通的2D数组。
您可以而且应该使用一个大的分配为矩阵分配存储空间,而不是分别分配每一行。 C ++ std::vector
向量并不理想。初始化int *rows[M]
只需要循环&A[i][0]
,因此它只是数学,而不是多次加载或分配。
通过此间接表访问数组会使用指针追逐替换i*N + j
数学:加载rows[i]
,然后使用j
对其进行索引。
当你不需要数组的混乱视图时,你可以直接访问它,但是如果你想能够对数组进行永久性的改组,那么它的所有用户总是必须通过间接访问层