gprof 编译插桩剖析与 gprof2dot 调用图
更新时间:2026-09-19。gprof 是经典的编译期插桩函数剖析器:编译加
-pg后,程序退出时生成gmon.out,用 gprof 可得到每个函数的耗时占比和「谁调用谁、调用多少次」的调用图。现代无侵入剖析首选 perf,确定性指令级分析用 Callgrind。
一、完整流程
bash
# 1) 编译加 -pg(C/C++/Fortran 均可),保留 -g 符号
gcc -pg -g -O2 -o prog prog.c
# C++: g++ -pg -g -O2 ...
# 2) 正常运行(必须正常退出/return,才会写 gmon.out)
./prog [args]
ls gmon.out
# 3) 解析
gprof ./prog gmon.out > report.txt二、两张关键表
1)Flat profile(自时间)
bash
% cumulative self self total
time seconds seconds calls ms/call ms/call name
45.2 2.31 2.31 100000 0.023 0.040 calculate
30.1 3.85 1.54 2000000 0.001 0.001 hash_lookup- self seconds:函数自己(不含调用子函数)花的时间,优化先看这列最高的。
- calls / ms per call:调用次数和平均每次耗时。
2)Call graph(调用关系)
展示每个函数的调用者(parents)和被调用者(children)、分摊时间,能回答「这个热点主要被谁调用」「时间是函数自己花的还是它调用的子函数花的」。
三、gprof2dot 可视化
bash
# 把 gprof 输出转成调用图(需要 graphviz 的 dot)
gprof ./prog gmon.out | gprof2dot -n 5 -e 0 | dot -Tpng -o callgraph.png
# -n 只显示占比≥5%的节点,过滤长尾,图更可读节点颜色/大小表示耗时,箭头表示调用方向与频率,一眼看出热点函数位于调用链哪里。gprof2dot 还能吃 perf、callgrind、oprofile 等多种格式,是统一可视化的好工具。
四、原理与局限
gprof = PC 采样(估算自时间)+ 编译插桩计数(精确的调用次数/父子关系)。局限要清楚:
- 要重新编译加 -pg,有插桩开销,不能对线上无符号二进制用(perf 无此限制)。
- 依赖程序正常退出写 gmon.out;常驻服务(daemon/服务端)要靠信号或改造,使用不便。
- 多线程支持差:历史上主要统计主线程,线程剖析结果易失真。
- 内联/共享库盲区:被内联的函数不存在、动态库默认不插桩。
- 时间是插桩运行下的采样估计,不是硬件真实周期。
五、选型
| 需求 | 工具 |
|---|---|
| 无侵入、生产/多线程、真实硬件周期 | perf record/report + 火焰图 |
| 硬件计数(cache/分支/IPC) | perf stat |
| 确定性指令级、缓存模拟 | Callgrind/Cachegrind |
| 快速拿到精确调用次数和调用图(可重编译的小程序) | gprof + gprof2dot |
坑
- 程序被 kill -9 / 异常终止,没有 gmon.out;要让它正常退出。
- 多线程程序只看到部分线程耗时,别据此下结论,换 perf。
- 高优化内联后调用图缺函数,用适中优化级别 + -g。
- 把插桩下的绝对时间当生产耗时,占比可以参考、绝对值不准。
总结
gprof 通过 -pg 编译插桩在程序正常退出后产出 gmon.out,用 gprof 看 flat profile 的函数自时间、调用次数和 call graph 的父子分摊,再用 gprof2dot 加 graphviz 把热点调用链画成图;它对可重编译、会正常退出的中小程序能快速给出精确调用关系,但需要重新编译、多线程和共享库支持弱、有插桩偏差,因此线上、多线程和真实硬件周期分析应优先使用 perf,确定性的指令与缓存分析用 Callgrind。