一、三步生成火焰图#
# 第一步:采样
perf record -F 99 -g ./my_program
# 第二步:转换为火焰图工具需要的格式
perf script > out.perf
# 第三步:生成 SVG 火焰图
git clone https://github.com/brendangregg/FlameGraph
FlameGraph/stackcollapse-perf.pl out.perf > out.folded
FlameGraph/flamegraph.pl out.folded > flame.svg
# 用浏览器打开 flame.svg
二、火焰图解读方法#
火焰图的阅读方向是"自底向上":
底部:调用链的起点(main、线程入口函数)
顶部:CPU 正在执行的函数
x 轴:采样数量(不是时间)
→ 一个函数条越宽,它占的 CPU 时间越多
y 轴:调用栈深度
→ 顶层函数是被调用的,底层是调用者
颜色:没有特殊含义,随机分配(但也有风格按函数名 hash 上色)
平顶(顶部宽,底部窄):
这个函数本身的计算量大
┌───────────────────┐
│ compute_dct │ ← 宽 → 大部分时间花在 compute_dct 里面
└───────────────────┘
↑ process_frame
↑ main
尖顶(顶部窄,底部宽):
大部分时间花在子函数中,当前函数只是调用了别人
┌──┐
│f1│ ← 窄 → f1 本身不花时间,它调用的子函数花时间
└──┘
↑ f2
↑ f3
三、结合自己的项目的解读示例#
假设你的 SimpleAsyncLogger 生成火焰图如下:
底部(main):
main → AsyncWriter::run → io_uring_enter ← 宽
→ memcpy ← 窄
→ TcpClient::send ← 中等
分析:
io_uring_enter 很宽 → 大部分时间花在等待 I/O 完成(正常,异步日志就是等 I/O)
memcpy 很窄 → 你的 SPSC 零拷贝设计是有效的
TcpClient::send 中等 → 网络可能成为瓶颈,考虑更大的发送缓冲区
如果 memcpy 很宽 → 说明 SPSC 的零拷贝没有生效,检查是不是走了拷贝路径。
如果 TcpClient::send 很宽 → 网络带宽或 buffer 需要调优。