Valgrind的cachegrind工具不直接输出缓存命中率,仅提供缓存访问次数和缺失次数,需手动计算:命中率 = 1 − (I1mr + D1mr + LLmr) / (I1refs + D1refs + LLrefs),其中各缩写对应报告中的具体字段。

Valgrind本身不直接提供缓存命中率数据
Valgrind 的 cachegrind 工具能模拟 CPU 缓存行为并统计访问次数,但它输出的是缓存**缺失(miss)次数**和**访问(access)次数**,不是“命中率”这个比值。你需要自己计算:命中率 = 1 - (D1mr + I1mr + LLmr) / (D1rd + D1wr + I1rd + LLrd + LLwr),其中各缩写对应 cachegrind 报告里的具体字段(如 D1mr 是一级数据缓存缺失数)。直接搜“缓存命中率”容易误以为 Valgrind 有现成指标,其实没有。
用cachegrind获取基础缓存事件计数
运行命令必须加 --tool=cachegrind,且建议固定缓存参数以复现结果:
cachegrind --tool=cachegrind --cachegrind-out-file=cg.out --I1=32768,8,64 --D1=32768,8,64 --LL=8388608,16,64 ./your_program
-
--I1/--D1/--LL分别指定一级指令、一级数据、末级缓存的大小、路数、块大小;不指定则用默认值(可能和你真实 CPU 不符) - 输出文件
cg.out包含每行代码的读/写/缺失计数,但**不包含函数内联展开后的细粒度统计**——如果函数被内联,计数会归到调用点,不是原函数定义处 - 务必用
-g编译,否则行号映射错乱;-O2可以保留,但-flto会导致符号丢失,cachegrind无法关联源码
从cachegrind输出提取关键数值的实操方式
不要手动翻 cg.out 文件——它混合了汇总头和逐行数据。先用 cg_annotate 提取顶层汇总:
cg_annotate cg.out --auto=yes | head -20
你会看到类似这样的行:
立即学习“C++免费学习笔记(深入)”;
I1 cache: 32768 B, 8-way, 64 B blocks D1 cache: 32768 B, 8-way, 64 B blocks LL cache: 8388608 B, 16-way, 64 B blocks ... I1 misses: 123456 D1 misses: 654321 LL misses: 98765 I1 reads: 2345678 D1 reads: 3456789 D1 writes: 456789 LL reads: 567890 LL writes: 678901
- 注意:LL(Last Level)统计包含所有未命中 LL 的访存,不是“LL 命中数”,所以不能用
LL misses / LL accesses当作 L3 命中率——它漏掉了在 L1/L2 命中的那部分 - 真正有意义的是 D1 命中率:用
(D1 reads + D1 writes) - D1 misses得到 D1 hits,再除以总访问数 - 如果程序大量使用 SIMD 或非对齐访存,
cachegrind仍按 64 字节块模拟,但真实 CPU 可能触发额外填充或分裂访问,此时模拟值会偏离实际
命中率数字高≠缓存友好,要看访存模式
一个函数报告 99% D1 命中率,但若它遍历链表(随机跳转),cachegrind 仍会显示高命中——因为模拟器把连续地址当成局部性好,而真实 CPU 的预取器对链表完全失效。这时候更应关注 D1mr 绝对值和访存指令占比:
- 用
cg_annotate cg.out --threshold=0.01找出贡献超 1% 缺失的源码行 - 对比
D1mr和该行执行次数(可用callgrind补充):如果一次循环迭代引发上百次 D1 miss,大概率是步长不对齐或数据结构布局差 -
cachegrind不模拟 TLB,如果Ir(指令读取)缺失暴增,优先怀疑代码段缺页或分支预测失败,而不是缓存问题
真实缓存行为受预取、共享核心干扰、内存带宽限制等影响,cachegrind 只是确定性模拟。它擅长暴露算法级局部性缺陷,但对微架构细节(比如 Intel 的硬件预取器启停)无能为力。


















