最直接有效的方式是 perf record -g ./your_program,它能采样调用栈定位真正吃 cpu 的函数,需加 -g 生成调用图,配合 -g -o2 编译、-f 99 提高采样频率,并用 perf report -n --no-children 分析。

用 perf 找到 CPU 占用最高的函数
Linux 下最直接有效的方式就是 perf record -g ./your_program,它能采样调用栈,定位真正吃 CPU 的函数。注意别漏掉 -g(生成调用图),否则只能看到平铺的函数名,看不出谁调了谁。
常见错误是直接跑 perf top——它实时显示,但没保存上下文,一关就丢;而 perf record 会生成 perf.data,后续可反复分析。
- 编译时加
-g -O2:调试信息必须有,否则函数名全是[unknown];优化等级不能为-O0,否则内联失效、热点失真 - 如果程序运行太快,加
-F 99提高采样频率(默认 4KHz 不够准) -
perf report -n --no-children看带自耗时(self)和总耗时(children)的树状视图,重点关注 “Overhead” 列里 >5% 的函数
gprof 报告里 main 显示 0.0%?那是编译链接没配对
gprof 要求编译和链接都带 -pg,缺一不可。只编译加 -pg、链接不用,或者反过来,都会导致 gmon.out 为空或 main 耗时归零。
更隐蔽的问题是:C++ 模板函数、内联函数、std:: 容器操作通常不会出现在 gprof 报告里——它靠插桩,而模板实例化和内联代码不被插桩,实际热点可能完全漏掉。
- 确认链接命令含
-pg,比如g++ -pg main.o utils.o -o app,不是只在g++ -c -pg阶段加 -
gprof ./app gmon.out必须在同目录下执行,且gmon.out是本次运行生成的(旧文件会覆盖) - 避免对短时程序用
gprof:运行时间
火焰图比文本报告更能暴露调用链里的隐藏瓶颈
perf 默认输出是文本,但很多性能问题藏在“小函数被高频调用”里,比如 std::vector::push_back 或 std::string::c_str() 在循环里反复触发内存分配——文本里它们单次开销小,总和却占 30%。
用 perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg 生成火焰图,横向宽度 = 时间占比,纵向深度 = 调用栈。一眼就能看出哪一层“胖得异常”。
- 别跳过
stackcollapse-perf.pl:它是把 perf 原始栈折叠成火焰图能读的格式,漏了就出不来图 - 如果火焰图里大量出现
[unknown],说明符号没加载全——检查是否用了-g编译,以及是否 strip 过二进制 - 对多线程程序,默认
perf record只采样主线程;加-a或指定-p PID才能覆盖所有线程
Release 模式下 inline 函数消失?用 compiler explorer 看实际汇编
有时候 perf 显示某个函数耗时很高,但源码里它只有两行、还标了 inline——这说明编译器没内联,或者内联后又被拆成多个基本块,导致采样点分散。这时候看汇编比看 C++ 更可靠。
用 objdump -dS your_binary | grep -A20 'func_name' 或直接上 Compiler Explorer,贴代码选相同编译器和 -O2,观察是否真内联、有没有意外的分支或内存访问。
- 某些
inline函数在 Release 下仍不内联:比如函数体过大、含虚函数调用、或跨 translation unit -
__attribute__((always_inline))强制内联,但可能增大代码体积,反而降低指令缓存命中率 - perf 采样精度受 CPU 微架构影响:Intel 的
cycles事件在某些 Skylake 后代上会有偏差,建议优先用cpu/event=0x00,umask=0x00,name=instructions/这类稳定事件
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











