最轻量可靠方式是perf record -g -p PID -F 99 -- sleep 10采样,配合-g编译、-fno-omit-frame-pointer及未strip二进制,再用perf script+addr2line或FlameGraph解析符号与行号。

直接用 perf 采样 + addr2line 或 perf script 解析,是 Linux 下获取 C++ 进程实时 CPU 热点函数最轻量、最可靠的方式;gprof / VTune 要插桩或启动开销大,不适合“实时观测”场景。
怎么用 perf 实时抓取目标进程的 CPU 栈样本
核心是 perf record -g 配合 -p 指定 PID,避免全系统采样干扰:
-
perf record -g -p <pid> -F 99 -- sleep 10</pid>:对进程<pid></pid>每秒采样约 99 次(接近内核默认频率),持续 10 秒;-- sleep 10是 perf 的“采样窗口控制技巧”,比^C中断更干净 - 必须确保目标进程已启用 debug info(编译加
-g),否则符号解析会失败;若用-O2,建议加-fno-omit-frame-pointer,否则-g生成的栈帧可能被优化掉,-g无效 - 普通用户运行
perf可能报perf_event_paranoid错误,临时修复:echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
为什么 perf report 显示的是地址而不是函数名
因为 perf record 默认只记录内存地址,符号解析依赖调试信息和可执行文件路径。常见表现是 perf report 里看到一堆 [unknown] 或十六进制地址,而非 std::vector::push_back 这类函数名。
- 确认二进制含调试段:
file ./myapp应含with debug_info;若不含,重编译加-g - 确保
perf能找到可执行文件——它默认从perf.data记录的路径读取;若进程是远程部署或路径已删,需用perf buildid-list验证 build-id,并手动perf buildid-cache -a ./myapp注入符号 - 避免使用 strip 后的 binary:
strip ./myapp会删掉 .symtab/.debug_* 段,perf将完全无法解析函数名
如何把 perf 数据转成带函数名和行号的火焰图
关键不是“画图”,而是让每一行样本都落到具体 C++ 函数+源码行——这靠 perf script + addr2line 链式处理最可控:
立即学习“C++免费学习笔记(深入)”;
- 导出带符号的调用栈:
perf script -F comm,pid,tid,cpu,time,period,ip,sym,dso,trace -F callindent;其中sym列即函数名,dso是模块名(如/home/user/myapp) - 要精确到行号,得对
ip(指令指针)做符号化:perf script -F ip,sym,dso | while read ip sym dso; do addr2line -e "$dso" -fCi "$ip"; done 2>/dev/null;注意addr2line必须用原始编译的 binary(非 stripped 版) - 生成火焰图推荐用
FlameGraph工具链:perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu.svg;但前提是perf script输出中sym列不为空——否则火焰图全是[unknown]
为什么 std::function / lambda / inline 函数在 perf 里难定位
它们不是独立函数实体,编译器常将其展开或生成匿名符号,perf 默认看不到有意义的名字:
- lambda 在
perf report中通常显示为operator()+ 内联位置(如my.cpp:42),但若被完全内联,就只剩外层函数名 -
std::function调用实际跳转到其内部存储的函数指针,perf只能捕获到std::function::operator()这一层,除非你用perf script -F +brstack查分支栈并人工回溯 - 解决办法只有两个:一是编译时加
-fno-inline-functions(仅调试用),二是用perf probe在关键 lambda 调用点手动埋点(需源码级支持)
真正麻烦的不是采集,而是确保整个工具链看到的是同一份未 strip、带 frame pointer、含完整 debug info 的 binary;线上环境经常漏掉其中一环,结果采了一堆地址却对不上任何函数——这点比选什么可视化工具重要得多。


















