需加--dump-instr=yes和--collect-jumps=yes才能获得函数级缓存统计,否则仅显示文件级汇总;配合-g编译、--auto=yes及--source-path=使用cg_annotate方可显示函数名,并应结合d1mr/dr比值评估cache局部性。

用 cachegrind 生成函数级缓存统计需要加 --dump-instr=yes 和 --collect-jumps=yes
默认情况下 cachegrind 只统计到源文件粒度,不展开到函数内部。要看到每个函数的指令数、cache miss 次数等指标,必须显式启用指令级采集。否则 callgrind_annotate 或 cg_annotate 输出里只会显示文件汇总,看不到 foo()、bar() 这样的函数名。
关键参数组合:
-
--dump-instr=yes:开启每条指令的执行计数(必需) -
--collect-jumps=yes:收集跳转信息,有助于函数边界识别(强烈建议) -
--cache-sim=yes(默认开启):保证 L1/I1/D1 cache miss 统计生效
示例命令:
valgrind --tool=cachegrind --dump-instr=yes --collect-jumps=yes --log-file=cg.out ./myapp
cg_annotate 默认不显示函数,得用 --auto=yes + 指定源码路径
cg_annotate cg.out 直接运行,输出里大概率只有地址(如 0x401234)和汇编行,没有函数名。这是因为符号表未关联或未启用自动解析。
让函数名真正出现,需满足两个条件:
- 程序编译时带
-g(保留调试符号) - 运行
cg_annotate时加--auto=yes,并确保当前目录能访问到源码(或用--source-path=指明)
正确用法:
cg_annotate --auto=yes --source-path=. cg.out | head -20
若仍无函数名,检查 cg.out 头部是否含 cmd: ./myapp 且二进制有符号:nm -C ./myapp | grep main 应能输出符号。
函数级指标中,Ir、Dr、Dw 和 I1mr/D1mr 含义不同,别混看
输出表格第一列是事件类型,常见缩写含义如下:
-
Ir:指令读取次数(Instruction read),反映函数执行频度 -
Dr:数据读取次数(Data read),跟访存密集度相关 -
Dw:数据写入次数(Data write) -
I1mr:一级指令 cache miss 次数(越小越好) -
D1mr:一级数据 cache miss 次数(对性能影响更大)
注意:I1mr 通常远小于 D1mr;如果某个函数 D1mr 异常高,但 Dr 不高,可能是访问模式导致 cache line 频繁换入换出(比如步长为 64 的跨 cache line 访问)。
想快速定位热点函数?优先看 Ir 排序,再交叉查 D1mr 比值
cg_annotate 默认按 Ir 降序排,这是最直观的“执行量”指标。但仅看 Ir 容易忽略 cache 效率问题——一个函数执行 100 万次,每次只 miss 1 次,不如另一个执行 10 万次、每次 miss 50 次的函数伤性能。
更实用的做法:
- 先用
cg_annotate --auto=yes cg.out | grep -A 5 "fun:"快速扫函数列表 - 对
Ir前 10 的函数,手动计算D1mr / Dr比值(若Dr > 0) - 比值 > 0.05(5%)就值得怀疑:说明每 20 次数据读就有 1 次 miss,可能触发大量内存延迟
这个比值比绝对 miss 数更能反映局部性缺陷,尤其在数据集变大后会急剧恶化。
Cachegrind 的函数级数据依赖符号解析和指令采集双前提,缺一不可;而“哪个函数最耗 cache”不能只看 miss 总数,得结合访问密度和 miss 率交叉判断。











