cachegrind报告应优先关注ir(指令数)、dr+dw(数据读写总数)和llmr(末级缓存未命中率)三项:ir反映执行热度,dr+dw标识内存访问密集度,llmr高则表明访存成为性能瓶颈,三者结合可快速定位优化关键点。

看 Cachegrind 报告时优先盯住这三项
默认 cachegrind 输出的报告列非常多(比如 Ir、I1mr、LLmr、D1mr、Dr、Dw、D1mw、LLmw 等),但日常定位性能瓶颈,真正需要第一时间关注的只有三个核心指标:
-
Ir:指令数(Instruction count)——反映代码“执行了多少次”,是热点函数/行最直接的信号 -
Dr+Dw:数据读次数 + 数据写次数(Data reads/writes)——合起来看,能快速识别内存访问密集区域 -
LLmr:Last Level cache miss rate(L3 缓存未命中率)——比I1mr或D1mr更具实际意义,高LLmr通常意味着访存拖慢了整体速度
其它列如 I1mr(一级指令缓存未命中)、D1mr(一级数据缓存未命中)在调试 CPU 微架构级问题时有用,但多数应用层优化无需深究;LLmw(L3 写未命中)一般远低于读未命中,优先级更低。
用 callgrind_annotate 看报告时怎么过滤无关项
cachegrind 原生输出的是二进制文件(如 cachegrind.out.12345),必须用 callgrind_annotate 转成可读文本。但默认会把所有符号(包括 libc、C++ STL、甚至动态链接器)全打出来,干扰判断:
- 加
--auto=yes自动折叠内联函数,避免重复计数 - 用
--inclusive=no只显示当前函数自身开销(不包含调用子函数的指令数),更利于聚焦热点 - 配合
grep -v "lib.*\.so\|std::\|/usr/include"快速过滤系统和标准库符号(临时排查时够用) - 真正要分析某模块?先用
--fn=your_function_name锁定范围,再看其内部每行的Ir和LLmr
LLmr 高但 Ir 低,说明什么
这是容易被误判的情况:某函数 Ir 很小(执行指令少),但 LLmr 却异常高(比如 >5%)。这往往不是算法问题,而是数据布局或访问模式导致的缓存效率灾难:
- 典型场景:遍历一个指针数组(
struct foo* arr[N]),每个foo对象分散在堆上,造成随机访存 → L3 大量未命中 - 对比:同样逻辑,若把
foo改为连续数组(struct foo arr[N]),LLmr通常骤降 70% 以上 -
cachegrind不会告诉你“为什么未命中”,但它用LLmr指出“这里值得怀疑”——下一步该检查数据 locality,而不是改算法
别依赖 --cache-sim=yes 这类模拟参数
cachegrind 默认已启用完整缓存模拟(L1i/L1d/L2/L3),不需要手动加 --cache-sim=yes。这个参数早已废弃,加了反而可能触发旧版本兼容逻辑,导致统计偏差。
真正影响结果可信度的是编译选项:
- 务必用
-g编译,否则callgrind_annotate无法关联源码行 - 避免
-O3下的激进循环展开或向量化,它们会扭曲Ir分布;建议用-O2 -g平衡可读性与真实性 - 如果程序含大量
malloc小块分配,考虑加--sim-hints=smallalloc(仅限较新 Valgrind 版本),否则Dr/Dw统计可能低估
缓存行为高度依赖真实硬件配置,cachegrind 的价值不在绝对数值,而在不同实现间的相对变化——改一行代码后 LLmr 降了 2%,比看原始值是 0.8% 还重要。











