gprof 编译必须加 -pg 且避免 -O2 以上优化,否则因函数内联导致统计失真;需 -O0 -pg 编译、链接也带 -pg,运行后生成 gmon.out 并与可执行文件同目录使用。

gprof 编译时必须加 -pg 且不能用 -O2 以上优化
gprof 依赖编译器在函数入口/出口插入计数桩(instrumentation),而高阶优化(如 -O2、-O3)会内联函数、删除看似无用的调用,导致 gprof 统计失真甚至完全漏掉热点。实测中,-O1 通常可接受,但最稳妥的是 -O0 -pg。
-
g++ -O0 -pg -o myapp main.cpp utils.cpp—— 正确:关闭优化,启用桩点 -
g++ -O2 -pg -o myapp main.cpp—— 危险:gprof报告中大量函数调用次数为 0,% time分布异常 - 链接阶段也需带
-pg,否则动态链接库中的函数不会被采样
运行程序后自动生成 gmon.out,必须在同一目录下执行 gprof
程序退出(非 crash 或 exit(0) 之外的强制终止)后,会在**当前工作目录**生成 gmon.out。这个文件是二进制格式,不可编辑,且与可执行文件强绑定——换路径、重命名或重新编译都会让 gprof 解析失败。
- 正确流程:
./myapp gprof myapp gmon.out > profile.txt
- 错误操作:
gprof ./build/myapp gmon.out—— 若myapp不在当前目录,gprof找不到符号表,报错not in a.out format - 若程序 fork 多进程,只有主进程生成
gmon.out;子进程需单独处理(gprof默认不支持多进程聚合)
看懂 flat profile 和 call graph 的关键字段
flat profile 告诉你「哪个函数耗时最多」,call graph 揭示「谁调用了谁、调用频次和传播开销」。二者结合才能准确定位瓶颈。
-
% time:该函数自身执行时间占总采样时间的百分比(不含子调用) -
self seconds:函数纯开销,可用于横向对比不同函数 -
calls(在 call graph 中):实际调用次数,注意区分self(本函数直接调用)和children(子函数调用) - 警惕
main占比过高却无明细:说明热点在main内联循环或未分离逻辑,应拆出独立函数再分析
常见失效场景:静态库、模板函数、std::function 和信号处理
gprof 对现代 C++ 构建方式支持有限,不是所有函数都能被准确追踪。
立即学习“C++免费学习笔记(深入)”;
- 静态库(
.a)若未用-pg编译,其内部函数不会出现在flat profile中,只显示为“未知调用者” - 模板实例化函数(如
std::vector)可能被折叠或符号名过长,::push_back gprof显示为???或截断名,需配合c++filt解码:echo "_ZSt4copyIPiS0_EET0_T_S2_S1_" | c++filt -
std::function回调、lambda 捕获、信号处理函数(signal()注册的 handler)因跳转非标准,通常无法被gprof捕获 - 高频短函数(如每微秒调用一次的 getter)可能因采样粒度(默认 10ms)被漏掉,此时需考虑
perf或valgrind --tool=callgrind
gmon.out 是否存在、是否和可执行文件匹配、是否所有 .o 都用 -pg 编译——这些细节比调参更关键。











