朴素转置在大矩阵上变慢是因为CPU缓存不友好:matrixi行访问连续,但resultj列写入跨步跳跃,导致频繁Cache Miss;分块转置通过将矩阵划分为适配L1缓存的小块(如64×64),使源与目标数据共驻缓存,显著提升命中率。

为什么朴素转置在大矩阵上会变慢?
因为 CPU 缓存不友好。用 vector<vector>></vector> 或二维数组做 result[j][i] = matrix[i][j] 时,matrix[i][j] 是按行连续访问,但 result[j][i] 是按列写入——后者每次跳 rows 个元素,极易触发 Cache Miss。1024×1024 的 int 矩阵,朴素实现可能比优化版慢 2.5 倍以上。
- 行优先存储下,
matrix[i][j]访问是顺序的;result[j][i]写入是跨步的 - L1 数据缓存通常仅 32–64 KiB,而一次内层循环可能要加载远超此量的数据
- 问题不在算法复杂度(仍是 O(nm)),而在内存访问模式
分块转置怎么选 BLOCK 大小?
目标是让一个块的源+目标数据能同时放进 L1 缓存。假设元素是 int(4 字节),L1 D-Cache 为 32 KiB,则单块最多容纳 32 * 1024 / (2 * 4) = 4096 个元素 → 边长约 64。实践中常用 16、32、64 或 128,需实测。
-
BLOCK = 64:适合多数桌面 CPU,兼顾缓存命中与循环开销 -
BLOCK = 128:若 L1 更大(如某些服务器 CPU),可尝试,但要注意nbR = NROW / BLOCK必须整除,否则要补边界逻辑 - 不要硬编码
#define BLOCK 128后直接套用——先查getconf LEVEL1_DCACHE_SIZE或用cpuid获取真实缓存参数
分块代码里最容易写错的索引映射
分块后,源矩阵和目标矩阵的线性地址计算极易出错。关键不是“行列互换”,而是“块内局部坐标 → 全局坐标”的两次映射。常见错误是把 irow/icol 直接套进原公式,忽略块偏移。
- 源矩阵中块
(ibr, ibc)的起始位置是(ibr * BLOCK, ibc * BLOCK),对应线性地址(ibr * BLOCK) * NCOL + ibc * BLOCK - 正确映射:
pTarget[(ibr * BLOCK + irow) * NCOL + icol] = pSource[(ibc * BLOCK + icol) * NROW + irow] - 典型错误:写成
pSource[(ibr * BLOCK + irow) * NROW + icol](混淆了源/目标的块索引方向) - 验证方法:对 8×8 小矩阵手算一块(如
BLOCK=4),打印前 4 个赋值的irow/icol和实际地址
非方阵还能用分块吗?
能,而且必须用——原地转置只适用于方阵,但分块转置天然支持任意 NROW × NCOL。只要保证块循环范围不越界:ibr 从 0 到 (NROW + BLOCK - 1) / BLOCK - 1,ibc 从 0 到 (NCOL + BLOCK - 1) / BLOCK - 1,并在内层加 if (irow 边界检查。
立即学习“C++免费学习笔记(深入)”;
- 不要假设
NROW % BLOCK == 0或NCOL % BLOCK == 0 - 分块后的最后一行/列块往往不完整,必须显式截断,否则越界读写
- 如果用
std::vector<t></t>线性存储(推荐),记得目标矩阵总大小是NCOL * NROW,不是NROW * NCOL
分块不是银弹:它提升的是缓存局部性,但增加了一层嵌套循环开销。小矩阵(512×512 且反复调用的场景。别忘了,编译器 -O3 -march=native 对分块循环的向量化效果,有时比手动调优还关键。


















