perf record -g 必须加 -a 或 -p 才能覆盖所有线程,否则仅采样主线程;编译需 -g -O2 且不 strip;perf report 应用 -n --no-children 查 self 时间;futex_wait 等表明 Off-CPU 问题,需换 sched 事件分析。

perf record -g 必须加 -a 或 -p 才能覆盖所有线程
默认 perf record -g ./your_program 只采样主线程,多线程程序里真正吃 CPU 的可能是 worker 线程,结果 perf report 里全是空或只有 main 占比高。这不是工具没用,是漏了关键参数。
实操建议:
- 用
perf record -a -g ./your_program:-a 表示系统级采样,覆盖所有进程和线程(需 root 或提前调低/proc/sys/kernel/perf_event_paranoid) - 或用
perf record -p $(pgrep -f "your_program") -g:精准抓指定进程及其所有线程(推荐,避免干扰其他进程) - 若程序启动快、线程动态创建,加
-D 1000延迟 1 秒再开始采样,确保线程已就绪
编译必须带 -g -O2,-O0 会失真,strip 会让符号全丢
没有调试信息,perf report 里函数名全是 [unknown];优化等级太低(-O0),编译器不内联、不优化,导致热点函数被拆成大量琐碎调用,实际瓶颈被掩盖。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
-
perf report显示一堆std::vector::_M_realloc_insert或operator new,但源码里根本没显式调用——这是 -O0 下 STL 内部展开的结果 - 火焰图顶部大片灰色,点不开函数名:二进制被
strip过,或编译时漏了-g - 同一函数在不同优化等级下
Overhead差 3 倍以上:-O0 下内联失效,调用栈拉长,采样分散
正确做法:g++ -g -O2 -pthread main.cpp -o app,不加 -fno-omit-frame-pointer 也行(现代 GCC -O2 默认保留帧指针),但加了更稳妥。
perf report -n --no-children 是看 self 时间的唯一可靠方式
默认 perf report 显示的是“总耗时(children)”,即函数自身 + 所有子调用时间。对多线程程序,这会导致锁函数(如 futex_wait)或调度函数(如 sched_stat_sleep)虚高——它们本身不干活,只是等待入口。
真正要找 CPU 实际执行的热点,得看 self 时间:
-
perf report -n --no-children:关闭 children 聚合,只显示函数自身指令执行时间 - 重点关注 “Overhead” 列里 >3% 且不是系统调用/锁/睡眠类的函数,比如
process_data、parse_json、std::string::append - 如果
std::vector::push_backself 时间高,大概率是循环里反复扩容;如果std::map::find高,考虑换std::unordered_map
火焰图里出现 futex_wait / sched_stat_blocked?那是 Off-CPU 问题
火焰图顶部一大片 futex_wait、epoll_wait、nanosleep,说明线程大部分时间不在 CPU 上运行——不是 CPU 瓶颈,是锁竞争、IO 阻塞或调度延迟。
这时候不能继续用 cycles 事件,该切到调度事件:
perf record -e sched:sched_switch,sched:sched_stat_blocked -g -p $(pgrep -f app)- 然后生成 Off-CPU 火焰图:
perf script | stackcollapse-perf.pl | flamegraph.pl --title "Off-CPU Time" > offcpu.svg - 对比 On-CPU 和 Off-CPU 火焰图:如果两者“胖”的位置高度重合,说明是同步原语设计问题(比如临界区过大)
容易忽略的一点:多线程程序里,perf record 默认不记录内核符号,futex_wait 可能显示为 [kernel.kallsyms]。需确认 /proc/sys/kernel/kptr_restrict 为 0,且 perf 编译时链接了 debuginfo。


















