callgrind通过插桩记录函数调用开销,生成callgrind.out.PID文件;需-g编译并运行程序,用callgrind_annotate分析热点,gprof2dot+dot生成调用图,kcachegrind提供交互式图形分析。

用 callgrind 抓调用开销数据
Valgrind 本身不直接显示“函数间调用开销”,必须用 callgrind 工具。它通过插桩(instrumentation)记录每条指令执行次数、函数调用/返回事件和调用关系,最终生成可分析的 callgrind.out.PID 文件。
关键点:必须带 -g 编译,否则无法关联源码行;程序要实际跑起来(不能只启动就退出),否则 callgrind 没有足够采样点。
- 编译命令:
gcc -g -o myprog myprog.c - 运行命令:
valgrind --tool=callgrind ./myprog - 默认输出文件名类似
callgrind.out.12345(PID 后缀) - 加
-v可看实时统计,但会拖慢更多;加--dump-instr=yes可启用指令级计数(通常不需要)
callgrind_annotate 看文本版调用开销
callgrind_annotate 是最轻量、最直接的查看方式,适合快速定位热点函数和调用链路。它把原始数据按函数维度聚合,显示自身耗时(self)、包含子调用总耗时(children)、调用次数(called)三类核心指标。
- 基本命令:
callgrind_annotate callgrind.out.12345 - 加
--auto=yes自动解析符号(推荐) - 加
--threshold=0.1过滤掉占比低于 0.1% 的函数(避免噪音) - 输出中每一行的
self值高,说明该函数自身逻辑重;children高而self低,说明它主要在调别人(比如包装函数、分发器)
用 gprof2dot + dot 生成调用图
文本看不清父子关系?用 gprof2dot 转成图。注意它默认过滤小开销函数(-n0.5),若想看到所有调用(包括构造/析构、模板实例化等),得显式关掉阈值:
- 全量调用图:
python gprof2dot.py -f callgrind -n0 -e0 -s callgrind.out.12345 > graph.dot -
-n0表示不按自身耗时过滤,-e0表示不按子调用耗时过滤,-s精简函数名(去掉模板参数、std::前缀等) - 生成图片:
dot -Tpng graph.dot -o graph.png(Ubuntu 可用),或dot -Tgif graph.dot -o graph.gif(SUSE 等老系统兼容性更好) - 图中边上的百分比是父函数调用该子函数所占的总时间比例,不是子函数自身占比
kcachegrind 是最实用的图形界面
比起静态图,kcachegrind 支持交互式展开、按耗时排序、点击跳转源码、过滤函数名、对比两次运行差异——这些对真实调优必不可少。
- 安装(Ubuntu):
sudo apt-get install kcachegrind - 启动:
kcachegrind callgrind.out.12345 - 重点关注 “Call Graph” 标签页:左侧是调用树,右侧是每个节点的 self/called/children 数据,双击任意函数可跳转到源码(需
-g编译且路径未变) - 容易忽略的一点:kcachegrind 默认按 “Inclusive Cost” 排序,但真正要优化的是 “Self Cost” 高的函数;右键列头可切换排序依据
callgrind 的开销极大(通常慢 10–50 倍),所以别拿它测线上服务;也别指望它能捕获 dlopen 动态加载的库调用——那是 gprof 的盲区,也是 callgrind 的盲区。真实环境里,如果函数调用路径里混了 .so 插件,那些调用会被记作 “???”,只能靠日志或 LD_PRELOAD 拦截补全。











