Cachegrind 对内存访问密集、缓存行为敏感的代码优化最有效,如大型数组/矩阵遍历、随机链表或哈希表访问、字段跨 cache line 访问、多维数组错序遍历等。

哪些代码用 Cachegrind 优化最有效
Cachegrind 对「内存访问密集、缓存行为敏感」的代码最有价值,不是所有慢代码都适合它。它不告诉你函数耗时多长,而是告诉你 CPU 缓存是否在拖后腿——比如明明逻辑简单,却跑得奇慢,大概率是 cache miss 太高。
典型适用场景包括:
- 遍历大型数组或矩阵的循环(尤其是
std::vector、C 风格二维数组) - 频繁随机访问指针链表(
std::list、自定义 node 链)或哈希桶(未预分配/扩容频繁的std::unordered_map) - 结构体字段访问模式混乱(比如只读第 0 字节和第 63 字节,跨 cache line)
- 多维数组按错误顺序遍历(如 C 语言里按列优先访问行主序数组)
cachegrind --I1、--D1、--LL 参数怎么选
cachegrind 默认模拟的是 x86_64 典型 CPU 的缓存层级(I1=32KB 指令缓存,D1=32KB 数据缓存,LL=2MB 共享末级缓存),但你不能靠猜——必须匹配目标机器实际值,否则分析结果会失真。
查当前机器缓存配置:
getconf LEVEL1_ICACHE_SIZE / getconf LEVEL1_DCACHE_SIZE / getconf LEVEL2_CACHE_SIZE
然后显式传参,例如:
valgrind --tool=cachegrind --I1=32768,8,64 --D1=32768,8,64 --LL=2097152,16,64 ./myapp
其中三个数字分别代表:缓存大小(bytes)、组相联度、cache line 大小(bytes)。错一个就可能让 Ir(指令数)或 Dw(数据写)统计严重偏移。
看懂 cachegrind 输出里的关键指标
运行后生成 cachegrind.out.PID,用 cg_annotate 查看热点:
重点关注三列:
-
Ir:指令读取次数 —— 高说明函数本身指令多或循环展开不足 -
Dr+Dw:数据读/写次数 —— 高但D1mr(一级数据缓存缺失率)也高,说明访问局部性差 -
LLmr(末级缓存缺失率)>5% 就值得警惕;>15% 基本确认是缓存瓶颈
例如某循环行输出:
1,234,567 Ir
876,543 Dr
12,345 Dw
42 D1mr
189 LLmr
这里 LLmr=189 实际是 189/876543 ≈ 0.02%,但注意单位是「缺失次数」,不是百分比——真正要看的是 LLmr / (Dr + Dw) 这个比值。
容易被忽略的陷阱:编译器优化与调试信息
用 -O2 编译再跑 cachegrind 才有意义,因为 -O0 下大量冗余访存、寄存器未复用,会掩盖真实缓存行为。但同时必须加 -g,否则 cg_annotate 无法把指令地址映射回源码行。
另一个坑:std::vector::data() 直接裸指针访问比 operator[] 更易被向量化,但 cachegrind 不显示向量化效果——它只管 cache 行命中,不管 SIMD。所以看到 Ir 显著下降,别急着归功于 cache 优化,先检查是否编译器悄悄向量化了循环。
最后一点:cachegrind 本身不支持多线程并发采样,如果程序有线程竞争导致 cache line bouncing(伪共享),helgrind 或 perf record -e cache-misses 更合适。


















