3. 缓存友好的矩阵转置
任务目标
实现一个按行主序存储的矩阵转置,通过分块(blocking)策略提升缓存局部性,并与朴素转置对比缓存未命中次数(使用Cachegrind模拟)。
核心实现
#include <vector>
#include <cstdint>
// 朴素转置:按列访问源矩阵,缓存不友好
void transpose_naive(const std::vector<float>& src,
std::vector<float>& dst,
int n) {
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
dst[j * n + i] = src[i * n + j];
}
// 分块转置:块内行/列访问均连续,提升缓存命中
void transpose_blocked(const std::vector<float>& src,
std::vector<float>& dst,
int n, int block = 16) {
for (int i0 = 0; i0 < n; i0 += block) {
for (int j0 = 0; j0 < n; j0 += block) {
int i_end = std::min(i0 + block, n);
int j_end = std::min(j0 + block, n);
for (int i = i0; i < i_end; ++i)
for (int j = j0; j < j_end; ++j)
dst[j * n + i] = src[i * n + j];
}
}
}
验证与对比
// main.cpp —— 编译后配合valgrind/cachegrind统计缓存未命中
#include <cstdio>
#include <cstdlib>
#include <vector>
int main(int argc, char** argv) {
int n = 1024;
int mode = 0; // 0=naive, 1=blocked
if (argc > 1) n = atoi(argv[1]);
if (argc > 2) mode = atoi(argv[2]);
std::vector<float> src(n * n), dst(n * n);
for (int i = 0; i < n * n; ++i) src[i] = static_cast<float>(i);
if (mode == 0)
transpose_naive(src, dst, n);
else
transpose_blocked(src, dst, n);
// 正确性校验:转置后 dst[i*n+j] == src[j*n+i]
bool ok = true;
for (int i = 0; i < n && ok; ++i)
for (int j = 0; j < n && ok; ++j)
if (dst[i * n + j] != src[j * n + i]) ok = false;
printf("n=%d mode=%d %s\n", n, mode, ok ? "PASS" : "FAIL");
return ok ? 0 : 1;
}
编译与运行
g++ -O2 -std=c++11 main.cpp -o transpose
# 分别统计两种实现的缓存未命中
valgrind --tool=cachegrind ./transpose 1024 0 # 朴素
valgrind --tool=cachegrind ./transpose 1024 1 # 分块
判定标准
- 程序输出
PASS,说明转置结果正确。 - Cachegrind报告末尾的
D1 miss rate(一级数据缓存未命中率)中,分块版本应明显低于朴素版本(例如n=1024时通常可降低30%~60%)。 - 对比两个版本的
D1 miss rate数值即可验证缓存优化效果。
关键说明
分块大小block取16~32通常效果较好,实际最优值取决于目标CPU的缓存行大小与L1容量。本案例仅演示优化方法与验证路径,不声称特定硬件上的实测数值。若需调整矩阵规模,直接修改命令行参数即可。