perf可直接获取真实可复现的热点函数,无需改代码或编译器插桩,只需二进制带-g符号且关闭aslr;它通过cpu周期事件采样,记录中断时完整调用栈,支持函数名、耗时占比、系统调用阻塞及硬件事件分析。

直接用 perf 就能拿到真实、可复现的热点函数,不需要改代码、不依赖编译器插桩,只要二进制带 -g 符号且关掉 ASLR。
perf record -g 能抓到什么
它采样的是 CPU 周期事件,记录每次中断时的完整调用栈(从用户态到内核态),包括:
• 函数名(前提是二进制含调试符号)
• 每层调用的耗时占比
• 是否在系统调用里卡住(比如 read、write、futex)
• 缓存未命中、分支预测失败等硬件级事件(加 -e cache-misses,branches 即可)
注意:如果没加 -g,就只看到平铺函数,看不到谁调了谁;如果没 -g 但用了 --call-graph dwarf,对 C++ 模板和内联函数还原更准,但要求内核 ≥ 5.10 且程序编译时带 -fno-omit-frame-pointer。
gprof 看起来简单,但容易误判
gprof 不是“开箱即用”的替代方案。它强制你在编译时加 -pg,这会:
• 插入额外跳转指令,改变函数内联行为
• 在 -O2 下大量函数消失(被优化掉或内联)
• 多线程下统计值严重失真(只记录主线程计数器)
• 动态库必须也用 -pg 编译,否则调用链断裂
所以除非你跑的是单线程、-O0 或 -O1、无内联的老代码,否则别指望 gprof 报告里的 “80% time in compute()” 是真实瓶颈。
编译和运行前必须检查的三件事
不管选 perf 还是 gprof,漏掉任意一项,结果就不可信:
• 二进制必须含调试信息:编译加 -g,上线前别 strip —— 可以用 file your_binary 看是否含 “with debug_info”
• 关闭地址随机化:运行前执行 echo 0 | sudo tee /proc/sys/kernel/randomize_va_space,否则两次 perf script 输出地址对不上
• 用真实负载跑:空循环、小数组、mock 数据会掩盖缓存行竞争、TLB miss 等真实问题,至少让程序跑满 5 秒以上再停
最常被忽略的是符号完整性——perf report 里一堆 ??? 不是工具坏了,是你部署时删掉了 .debug 文件或没配好 debuginfod 服务。C++ 模板展开、lambda、std::function 回调这些,全靠调试符号才能还原成可读名。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











