perf record -g 默认用的是 frame pointer,等价于 --call-graph fp,依赖栈帧指针链,开销小但遇内联、尾调用或 jit 代码易截断;dwarf 模式需显式指定且依赖调试信息、内核支持及正确参数。

perf record -g 默认用的是 frame pointer 还是 DWARF?
perf record -g 在大多数现代 Linux 发行版(如 Ubuntu 22.04+、RHEL 8+)上默认启用 frame pointer 模式,不是 DWARF。它等价于 --call-graph fp,前提是内核编译时开了 CONFIG_FRAME_POINTER=y,且用户程序没被编译成 -fomit-frame-pointer(GCC/Clang 默认已禁用该优化)。
- 默认
fp模式依赖栈帧指针链,快、开销小,但遇到内联函数、尾调用、手写汇编或某些 JIT 代码时会截断 -
--call-graph dwarf读取 ELF 中的.debug_frame或.eh_frame,能还原更完整的调用栈,尤其对优化过的代码更可靠 - 但 DWARF 采样开销明显更高:每次样本都要解析调试信息 + 栈回溯,CPU 占用高、可能丢样本、
perf script解析也慢得多
什么时候必须用 --call-graph dwarf?
当你观察到以下现象时,fp 模式大概率不够用:
-
perf report里大量调用栈只显示 1–2 层,尤其是从libc或libstdc++进入后就断了 - 程序用
-O2 -fno-omit-frame-pointer编译,但仍有函数“消失”在调用路径中(比如std::vector::push_back后直接跳到malloc) - 调试对象是 Go、Rust(未开启
frame-pointers)、Java(JVM 需额外配置)或 Python 扩展模块,它们默认不维护传统帧指针 - 你看到
perf script输出里有大量[unknown]或__kernel_rt_sigreturn卡在栈顶,说明帧链已损坏
这时要强制切到 DWARF:perf record -e cycles --call-graph dwarf -g ./myapp
DWARF 模式下必须确保的三件事
--call-graph dwarf 不是开箱即用,漏掉任一环节都会退化成空栈或报错:
- 用户二进制必须带调试信息:
gcc -g -O2或rustc -g;strip 过的文件不行,readelf -S ./a.out | grep debug应能看到.debug_*段 - 内核需支持 DWARF 栈展开:检查
cat /proc/sys/kernel/perf_event_paranoid≤ 2,且内核配置含CONFIG_UNWINDER_DWARF(主流发行版内核通常已启用) - 不要混用
-g和--call-graph:写成perf record -e cycles -g --call-graph dwarf会静默忽略--call-graph,正确写法是去掉-g,只用--call-graph dwarf
cycles 事件 + DWARF 的性能代价真实有多高?
别只看文档说“开销大”,实测差异很具体:
- 同样采样 10 秒、
cycles事件、1ms 间隔下:fp模式 perf.data 约 8–12 MB;dwarf模式常达 40–90 MB,且perf script解析时间从 0.3s 拉长到 5–12s - 在高频短函数(如 hash 表查找、锁竞争点)场景,DWARF 可能因处理不过来而丢弃 15–30% 的样本(
perf report -D | grep lost可查) - 如果目标是定位热点函数而非完整调用链,优先用
--call-graph lbr(Intel CPU 支持)或干脆不用-g,靠perf report --no-children看 flat profile 更稳
调用栈精度和采样保真度之间始终存在张力,DWARF 不是银弹,它解决的是“能不能看到”,但代价是“看到多少”和“还能不能信”。










