适用场景:C++ 服务在生产/测试环境中出现 CPU 占用率异常偏高,需要快速定位热点函数与性能瓶颈。
1. 什么是火焰图
火焰图(Flame Graph)由 Netflix 性能工程师 Brendan Gregg 于 2011 年发明,是一种将大量堆栈采样(stack trace)数据可视化的方法。它将调用栈从底部到顶部排列,宽度代表函数在采样中出现的次数(即 CPU 占用比例),使得最"热"的代码路径一目了然。
火焰图的视觉结构
bash
┌─────────────────────────────────────────────────────────┐
│ hotFunction() │ ← 顶层:叶子函数,直接消耗 CPU
├────────────────────────┬────────────────────────────────┤
│ computeHash() │ processData() │ ← 中间层:调用者
├────────────────────────┴────────────────────────────────┤
│ main() │ ← 底层:入口函数
└─────────────────────────────────────────────────────────┘
◄──────────────── 宽度 = 采样次数(占比) ──────────────►
核心规则:
- Y 轴:调用栈深度(从下往上是调用链)
- X 轴 :不代表时间,仅为按字母排序的并列展示
- 宽度:函数在采样中出现的次数,越宽说明 CPU 占用越高
- 颜色:默认为随机暖色调,无特殊含义(除非使用差分火焰图)
2. 火焰图的核心原理
2.1 基于采样的性能分析
火焰图依赖采样型 profiler(如 perf),其工作方式是:
⏱ 每隔 ~10ms
(99 Hz)
⚡ 中断 CPU
📋 记录当前线程
完整调用栈
📊 汇总所有样本
🔥 生成火焰图
为什么用 99 Hz 而不是 100 Hz? 避免与系统定时器(通常为 100 Hz)产生共振,导致采样偏差。
2.2 采样 vs 插桩
| 特性 | 采样(Sampling) | 插桩(Instrumentation) |
|---|---|---|
| 开销 | 极低(1-5%) | 较高(10-50%+) |
| 精度 | 统计近似 | 精确计数 |
| 适用场景 | 生产环境、长时间运行 | 开发环境、短时间分析 |
| 代表工具 | perf, DTrace | Valgrind, gprof |
| 火焰图常用 | ✅ | ❌ |
3. 工具链总览
Linux 平台(推荐)
| 工具 | 用途 | 安装方式 |
|---|---|---|
perf |
Linux 内核级采样 profiler | sudo apt install linux-tools-$(uname -r) |
FlameGraph |
Brendan Gregg 的火焰图生成脚本 | git clone https://github.com/brendangregg/FlameGraph |
hotspot |
图形化 perf 分析工具 | sudo apt install hotspot |
跨平台替代方案
| 工具 | 平台 | 说明 |
|---|---|---|
Instruments |
macOS | Xcode 自带,支持 Time Profiler |
VTune |
Linux/Windows/macOS | Intel 出品,专业级 |
Tracy |
跨平台 | 游戏行业常用,实时帧级分析 |
Visual Studio Profiler |
Windows | 内置于 Visual Studio |
gperftools (pprof) |
Linux/macOS | Google 出品,支持 CPU + 内存 |
4. 环境准备
4.1 安装 perf
bash
# Ubuntu / Debian
sudo apt update
sudo apt install linux-tools-common linux-tools-$(uname -r)
# CentOS / RHEL
sudo yum install perf
# 验证安装
perf --version
4.2 下载 FlameGraph 脚本
bash
git clone https://github.com/brendangregg/FlameGraph.git
cd FlameGraph
ls *.pl # 确认脚本存在
# stackcollapse-perf.pl flamegraph.pl difffolded.pl ...
4.3 编译 C++ 程序的关键参数
为了让火焰图显示有意义的函数名(而非一堆地址),编译时必须包含调试信息:
bash
# 推荐:保留调试符号 + 开启优化(模拟生产环境行为)
g++ -O2 -g -fno-omit-frame-pointer -o my_program main.cpp
# 各参数说明:
# -O2 : 开启优化,保持与生产一致
# -g : 生成调试符号(DWARF),让 perf 能解析函数名
# -fno-omit-frame-pointer: 保留帧指针,确保 perf 能正确回溯调用栈
!CAUTION
如果不加
-fno-omit-frame-pointer,高优化级别下 GCC/Clang 会省略帧指针寄存器(RBP),导致
perf无法正确展开调用栈,火焰图上会出现大量[unknown]或断裂的调用链。
5. 实战:用 perf + FlameGraph 排查 CPU 热点
完整流程(5 步)
❶ 采样
perf record
-F 99 -g -p PID
❷ 解析
perf script
→ 文本格式
❸ 折叠栈
→ 单行格式
❹ 生成SVG
→ 交互式火焰图
❺ 分析
浏览器打开 SVG
搜索 / 缩放 / 悬停
步骤 1:采样
bash
# 方式一:启动程序并采样(适合可重复的场景)
sudo perf record -F 99 -g -p $(pgrep my_program) -- sleep 30
# 方式二:直接运行并采样(适合短生命周期程序)
sudo perf record -F 99 -g -- ./my_program
# 参数说明:
# -F 99 : 采样频率 99 Hz
# -g : 记录调用栈(call graph)
# -p PID : 附着到已运行的进程
# sleep 30: 采样持续 30 秒
!TIP
采样时间建议 :对于稳态运行的服务,采样 30-60 秒足够反映典型行为。
对于间歇性 CPU 飙高的场景,可以结合监控在 CPU 飙高时触发采样。
步骤 2:解析 perf 数据
bash
# 将二进制 perf.data 转为可读文本
sudo perf script > perf_output.txt
# 查看前几行,确认数据正常
head -20 perf_output.txt
输出示例:
my_program 3892 [001] 94564.456783: cpu-clock:
7f2d1a hotFunction+0x14 (/home/user/my_program)
7f2d3b computeHash+0x2b (/home/user/my_program)
7f2d5c processData+0x1c (/home/user/my_program)
7f2d8e main+0x4e (/home/user/my_program)
7f0821 __libc_start_main+0xf1 (/lib/x86_64-linux-gnu/libc.so.6)
步骤 3:折叠调用栈
bash
# 使用 FlameGraph 脚本将 perf 输出折叠为单行格式
./FlameGraph/stackcollapse-perf.pl perf_output.txt > folded_stacks.txt
# 查看折叠后的格式
head -5 folded_stacks.txt
折叠后格式(每行一个调用栈 + 出现次数):
my_program;__libc_start_main;main;processData;computeHash;hotFunction 3542
my_program;__libc_start_main;main;processData;sortResults 892
my_program;__libc_start_main;main;handleRequest;parseJSON 234
步骤 4:生成火焰图 SVG
bash
# 基础生成
./FlameGraph/flamegraph.pl folded_stacks.txt > flamegraph.svg
# 带定制参数的生成
./FlameGraph/flamegraph.pl \
--title "My C++ Service CPU Flame Graph" \
--subtitle "Sampled at 99 Hz for 30s, 2026-03-04" \
--width 1200 \
--colors hot \
--countname samples \
folded_stacks.txt > flamegraph.svg
步骤 5:在浏览器中分析
bash
# 用浏览器打开 SVG(火焰图是交互式的!)
firefox flamegraph.svg
# 或
google-chrome flamegraph.svg
交互功能:
- 🖱️ 悬停:显示函数名、采样次数和占比
- 🔍 点击:放大某个调用栈分支
- 🔎 Ctrl+F:搜索函数名(匹配项高亮为紫色)
- ↩️ Reset Zoom:点击左上角重置缩放
一键脚本
将上述步骤整合为一个脚本,方便日常使用:
bash
#!/bin/bash
# flamegraph.sh - 一键生成 C++ 程序的火焰图
# 用法: ./flamegraph.sh <PID> <采样秒数> <输出文件名>
set -euo pipefail
PID=${1:?"Usage: $0 <PID> <DURATION_SEC> <OUTPUT_NAME>"}
DURATION=${2:-30}
OUTPUT=${3:-"flamegraph"}
FLAMEGRAPH_DIR=${FLAMEGRAPH_DIR:-"./FlameGraph"}
echo "🔥 [1/4] 采样进程 PID=$PID,持续 ${DURATION}s ..."
sudo perf record -F 99 -g -p "$PID" -- sleep "$DURATION"
echo "📝 [2/4] 解析 perf 数据 ..."
sudo perf script > /tmp/perf_output.txt
echo "📦 [3/4] 折叠调用栈 ..."
"$FLAMEGRAPH_DIR/stackcollapse-perf.pl" /tmp/perf_output.txt > /tmp/folded.txt
echo "🎨 [4/4] 生成火焰图 ..."
"$FLAMEGRAPH_DIR/flamegraph.pl" \
--title "CPU Flame Graph - PID $PID" \
--subtitle "$(date '+%Y-%m-%d %H:%M:%S') | ${DURATION}s @ 99Hz" \
/tmp/folded.txt > "${OUTPUT}.svg"
echo "✅ 火焰图已生成: ${OUTPUT}.svg"
echo "💡 请在浏览器中打开查看: firefox ${OUTPUT}.svg"
6. 完整示例:一个有性能问题的 C++ 程序
6.1 问题代码
以下是一个故意包含性能问题的 C++ 服务模拟代码:
cpp
// file: cpu_hog_demo.cpp
// 编译: g++ -O2 -g -fno-omit-frame-pointer -o cpu_hog_demo cpu_hog_demo.cpp -lpthread
// 运行: ./cpu_hog_demo
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <thread>
#include <chrono>
#include <numeric>
#include <random>
#include <unordered_map>
#include <sstream>
#include <mutex>
// ============================================================
// 问题 1:低效的字符串拼接(Schlemiel the Painter's algorithm)
// ============================================================
std::string buildReport_Bad(const std::vector<int>& data) {
std::string result;
for (size_t i = 0; i < data.size(); ++i) {
// 🐛 每次 += 都可能触发内存重分配和拷贝
result += "Item #" + std::to_string(i) + ": value="
+ std::to_string(data[i]) + "\n";
}
return result;
}
// ✅ 修复版本:使用 ostringstream 或 reserve
std::string buildReport_Good(const std::vector<int>& data) {
std::ostringstream oss;
for (size_t i = 0; i < data.size(); ++i) {
oss << "Item #" << i << ": value=" << data[i] << "\n";
}
return oss.str();
}
// ============================================================
// 问题 2:不必要的深拷贝
// ============================================================
struct LargeObject {
std::vector<double> matrix;
std::string name;
LargeObject() : matrix(10000, 3.14), name("default") {}
};
// 🐛 按值传递,每次调用都会深拷贝一个 80KB 的对象
double processObject_Bad(LargeObject obj) {
double sum = 0;
for (auto& val : obj.matrix) {
sum += std::sin(val) * std::cos(val);
}
return sum;
}
// ✅ 修复版本:使用 const 引用
double processObject_Good(const LargeObject& obj) {
double sum = 0;
for (auto& val : obj.matrix) {
sum += std::sin(val) * std::cos(val);
}
return sum;
}
// ============================================================
// 问题 3:O(n²) 算法------线性查找代替哈希查找
// ============================================================
struct Record {
int id;
std::string payload;
};
// 🐛 在 vector 中线性查找,O(n) 每次查找 × n 次 = O(n²)
std::string findRecord_Bad(const std::vector<Record>& records, int targetId) {
for (const auto& rec : records) {
if (rec.id == targetId) {
return rec.payload;
}
}
return "";
}
// ✅ 修复版本:使用 unordered_map,O(1) 查找
std::string findRecord_Good(const std::unordered_map<int, std::string>& recordMap,
int targetId) {
auto it = recordMap.find(targetId);
return (it != recordMap.end()) ? it->second : "";
}
// ============================================================
// 问题 4:锁争用(Lock Contention)
// ============================================================
std::mutex globalMutex;
int sharedCounter = 0;
// 🐛 持锁范围过大,包含了不需要同步的计算
void incrementCounter_Bad(int iterations) {
for (int i = 0; i < iterations; ++i) {
std::lock_guard<std::mutex> lock(globalMutex);
// 下面的计算不需要持锁
double dummy = std::sin(i) * std::cos(i) * std::tan(i + 1);
sharedCounter += static_cast<int>(dummy);
}
}
// ✅ 修复版本:缩小临界区
void incrementCounter_Good(int iterations) {
int localSum = 0;
for (int i = 0; i < iterations; ++i) {
double dummy = std::sin(i) * std::cos(i) * std::tan(i + 1);
localSum += static_cast<int>(dummy);
}
std::lock_guard<std::mutex> lock(globalMutex);
sharedCounter += localSum;
}
// ============================================================
// 模拟工作负载
// ============================================================
void simulateWorkload() {
// 生成测试数据
std::mt19937 rng(42);
std::uniform_int_distribution<int> dist(1, 1000000);
// 准备大数据集
std::vector<int> reportData(50000);
std::generate(reportData.begin(), reportData.end(), [&]{ return dist(rng); });
LargeObject largeObj;
std::vector<Record> records(100000);
for (int i = 0; i < 100000; ++i) {
records[i] = {i, "payload_" + std::to_string(i)};
}
std::cout << "=== 开始模拟工作负载 ===" << std::endl;
while (true) {
// 问题 1:低效字符串拼接
auto report = buildReport_Bad(reportData);
// 问题 2:不必要的深拷贝(循环 100 次放大问题)
double total = 0;
for (int i = 0; i < 100; ++i) {
total += processObject_Bad(largeObj);
}
// 问题 3:O(n²) 查找
for (int i = 0; i < 1000; ++i) {
int target = dist(rng) % 100000;
findRecord_Bad(records, target);
}
// 问题 4:锁争用(多线程)
std::vector<std::thread> threads;
for (int t = 0; t < 4; ++t) {
threads.emplace_back(incrementCounter_Bad, 100000);
}
for (auto& t : threads) {
t.join();
}
std::cout << "." << std::flush; // 心跳输出
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main() {
std::cout << "CPU Hog Demo - PID: " << getpid() << std::endl;
std::cout << "Use: sudo perf record -F 99 -g -p " << getpid()
<< " -- sleep 30" << std::endl;
simulateWorkload();
return 0;
}
6.2 编译与运行
bash
# 编译(注意关键参数)
g++ -O2 -g -fno-omit-frame-pointer -o cpu_hog_demo cpu_hog_demo.cpp -lpthread
# 运行(会打印自己的 PID)
./cpu_hog_demo &
# 输出: CPU Hog Demo - PID: 12345
6.3 采样与生成火焰图
bash
# 采样 30 秒
sudo perf record -F 99 -g -p 12345 -- sleep 30
# 生成火焰图
sudo perf script | ./FlameGraph/stackcollapse-perf.pl | \
./FlameGraph/flamegraph.pl --title "cpu_hog_demo Flame Graph" > cpu_hog.svg
# 打开查看
firefox cpu_hog.svg
6.4 火焰图分析结果
在火焰图中你会看到类似如下的热点分布:
预期火焰图结构(宽度表示 CPU 占比):
main
└── simulateWorkload
├── buildReport_Bad ██████████████████ (~35%)
│ ├── std::string::operator+=
│ ├── std::to_string
│ └── malloc / free ← 频繁内存分配
│
├── processObject_Bad █████████████ (~25%)
│ ├── LargeObject::LargeObject ← 拷贝构造
│ │ └── std::vector::vector ← 深拷贝 10000 个 double
│ ├── std::sin / std::cos
│ └── ~LargeObject ← 析构(释放拷贝的内存)
│
├── findRecord_Bad ████████████ (~22%)
│ └── std::string::compare ← 线性遍历比较
│
└── incrementCounter_Bad ████████ (~18%)
├── pthread_mutex_lock ← 锁等待
├── std::sin / cos / tan
└── pthread_mutex_unlock
6.5 根据火焰图定位问题并修复
| 排名 | 热点函数 | 占比 | 根因 | 修复方案 |
|---|---|---|---|---|
| 1 | buildReport_Bad |
~35% | std::string 反复重分配 |
改用 ostringstream 或预分配 reserve() |
| 2 | processObject_Bad |
~25% | 按值传参导致深拷贝 | 改为 const LargeObject& |
| 3 | findRecord_Bad |
~22% | O(n) 线性查找 |
改用 unordered_map |
| 4 | incrementCounter_Bad |
~18% | 临界区过大 | 缩小锁的范围,先本地累加再加锁写入 |
7. 火焰图解读技巧
7.1 快速定位问题的三步法
🔍 打开火焰图
第一步: 看'平顶' (Plateau)
⚠️ 函数顶部很宽 = 自身消耗大量 CPU
例: hotFunction() 占 40%
→ 直接优化该函数
第二步: 看'宽塔' (Wide Tower)
⚠️ 调用链很深且整体宽 = 路径频繁调用
例: main→parse→validate→...→tinyFunc
→ 减少调用频率或缓存中间结果
第三步: Ctrl+F 搜索关键词
malloc / free → 内存分配热点
mutex / lock → 锁争用
memcpy / memmove → 数据拷贝
__GI___clone → 线程创建开销
7.2 常见 CPU 热点模式
| 火焰图特征 | 可能的问题 | 排查方向 |
|---|---|---|
malloc/free 占比高 |
频繁堆内存分配 | 对象池、预分配、栈上分配 |
std::string 相关函数宽 |
字符串操作低效 | string_view、reserve、ostringstream |
pthread_mutex_lock 宽 |
锁争用严重 | 缩小临界区、无锁数据结构、读写锁 |
| 拷贝构造/析构函数宽 | 不必要的深拷贝 | const&、std::move、传指针 |
std::sort 或比较函数宽 |
排序算法热点 | 检查比较函数复杂度、部分排序 |
std::unordered_map::find 宽 |
哈希冲突多 | 自定义哈希函数、检查负载因子 |
| 非常深的递归塔 | 递归层数过深 | 改为迭代、尾递归优化 |
7.3 使用 Ctrl+F 搜索
火焰图 SVG 内置搜索功能:
搜索 "std::vector" → 高亮所有涉及 vector 操作的帧
搜索 "MyNamespace" → 高亮所有自己项目的代码(排除标准库)
搜索 "lock" → 快速定位锁相关的热点
搜索结果会以紫色高亮显示,并在右下角显示匹配帧的总占比。
8. 进阶:差分火焰图
差分火焰图(Differential Flame Graph)用于比较优化前后的 CPU 使用变化,是验证优化效果的利器。
8.1 工作流程
bash
# 1. 优化前:采样并生成折叠栈
sudo perf record -F 99 -g -p $PID_BEFORE -- sleep 30
sudo perf script > before.txt
./FlameGraph/stackcollapse-perf.pl before.txt > before_folded.txt
# 2. 优化后:采样并生成折叠栈(部署修复后的代码)
sudo perf record -F 99 -g -p $PID_AFTER -- sleep 30
sudo perf script > after.txt
./FlameGraph/stackcollapse-perf.pl after.txt > after_folded.txt
# 3. 生成差分火焰图
./FlameGraph/difffolded.pl before_folded.txt after_folded.txt \
| ./FlameGraph/flamegraph.pl \
--title "Differential Flame Graph (Before → After)" \
--negate \
> diff_flamegraph.svg
8.2 颜色含义
| 颜色 | 含义 |
|---|---|
| 🔴 红色 | 优化后 CPU 占用增加(性能退化) |
| 🔵 蓝色 | 优化后 CPU 占用减少(性能改善 ✅) |
| ⬜ 白色 | 无明显变化 |
8.3 实际案例
bash
# 对示例程序,修复 buildReport_Bad → buildReport_Good 后:
# 差分火焰图中,buildReport 栈会显示为亮蓝色,
# 表示 CPU 占用显著降低
# 搜索 "buildReport" 可快速确认效果
9. 进阶:Off-CPU 火焰图
有时程序的问题不是 CPU 占用高,而是等待时间过长(I/O、锁、sleep)。Off-CPU 火焰图可以帮助排查这类问题。
9.1 On-CPU vs Off-CPU
程序运行时间线
On-CPU
🔥 CPU 上执行代码
─────────────────
分析工具: CPU Flame Graph
perf record -F 99 -g
Off-CPU
⏳ 等待中 (I/O / 锁 / sleep)
─────────────────
分析工具: Off-CPU Flame Graph
perf record -e sched:sched_switch
或 bpftrace / BCC tools
9.2 使用 bpftrace 生成 Off-CPU 火焰图
bash
# 安装 bpftrace
sudo apt install bpftrace
# 采集 Off-CPU 数据(需要 Linux 4.8+ 内核)
sudo bpftrace -e '
kprobe:finish_task_switch {
@[kstack, ustack, comm] = count();
}
' -p $PID > offcpu_stacks.txt
# 或使用 BCC 工具
sudo /usr/share/bcc/tools/offcputime -df -p $PID 30 > offcpu_folded.txt
# 生成 Off-CPU 火焰图(推荐使用蓝色色调区分)
./FlameGraph/flamegraph.pl \
--title "Off-CPU Flame Graph" \
--colors io \
--countname "microseconds" \
offcpu_folded.txt > offcpu_flamegraph.svg
10. 常见问题与排查
Q1: 火焰图中全是 [unknown]
原因:缺少调试符号或帧指针未保留。
bash
# 检查二进制是否包含调试符号
file my_program
# 应该包含 "not stripped" 或 "with debug_info"
readelf -S my_program | grep debug
# 应该能看到 .debug_info 等段
# 解决方案:重新编译
g++ -O2 -g -fno-omit-frame-pointer -o my_program main.cpp
Q2: 火焰图中调用栈不完整 / 只有一两层
原因 :帧指针被优化掉了(GCC -O2 默认行为)。
bash
# 方案 1(推荐):加 -fno-omit-frame-pointer 重新编译
g++ -O2 -g -fno-omit-frame-pointer ...
# 方案 2:使用 DWARF 展开(不需要帧指针,但开销稍大)
sudo perf record -F 99 --call-graph dwarf -p $PID -- sleep 30
# 方案 3:使用 LBR(Last Branch Record,Intel CPU 专属,零开销)
sudo perf record -F 99 --call-graph lbr -p $PID -- sleep 30
Q3: perf record 报 "Permission denied"
bash
# 临时修改(重启失效)
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
# 或以 root 运行
sudo perf record ...
# 永久修改(添加到 sysctl)
echo "kernel.perf_event_paranoid = -1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Q4: 我的程序是多线程的,如何分析特定线程?
bash
# 方法 1:在火焰图中搜索线程名(如果程序设置了线程名)
# Ctrl+F 搜索线程名
# 方法 2:采样时指定线程 TID
sudo perf record -F 99 -g -t $TID -- sleep 30
# 方法 3:使用 --per-thread 模式,为每个线程单独生成
sudo perf record -F 99 -g -p $PID --per-thread -- sleep 30
Q5: 如何分析动态链接库中的热点?
bash
# 确保 .so 文件也带调试符号
g++ -O2 -g -fno-omit-frame-pointer -shared -fPIC -o libfoo.so foo.cpp
# 如果是第三方库,安装 debuginfo 包
sudo apt install libc6-dbg # glibc 调试符号
sudo apt install libstdc++6-dbg # libstdc++ 调试符号
11. 最佳实践清单
编译阶段
- 始终使用
-g保留调试符号 - 始终使用
-fno-omit-frame-pointer - 使用与生产一致的优化级别(
-O2) - 为
.so文件也保留调试符号
采样阶段
- 使用
-F 99(避免共振采样偏差) - 采样时长覆盖程序的典型工作周期
- 在 CPU 实际飙高时采样(而非空闲期)
- 确认
perf script输出包含函数名
分析阶段
- 先看最宽的"平顶"函数
- 用
Ctrl+F搜索项目自己的命名空间 - 关注
malloc、lock、拷贝构造等系统级热点 - 对比优化前后使用差分火焰图
团队协作
- 将火焰图 SVG 保存到版本控制或 Wiki
- 性能基准测试中自动生成火焰图(CI/CD 集成)
- 建立性能回归检测流程
12. 参考资料
| 资源 | 链接 |
|---|---|
| Brendan Gregg 火焰图官方网站 | https://www.brendangregg.com/flamegraphs.html |
| FlameGraph GitHub 仓库 | https://github.com/brendangregg/FlameGraph |
| perf 官方文档 | https://perf.wiki.kernel.org/index.php/Main_Page |
| Brendan Gregg《Systems Performance》 | ISBN: 978-0136820154 |
| perf Examples | https://www.brendangregg.com/perf.html |
| Intel VTune Profiler | https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html |
| hotspot GUI 工具 | https://github.com/KDAB/hotspot |
| gperftools (pprof) | https://github.com/gperftools/gperftools |
作者提示 :性能优化是一个迭代过程。火焰图帮助你找到问题 ,但修复后一定要再次采样验证 。
使用差分火焰图可以直观地确认优化效果,避免"优化了寂寞"的尴尬情况。
否
是 ✓
🔬 采样
perf record
🔥 生成火焰图
定位热点
🔧 修复代码
针对性优化
🔬 再次采样
perf record
📊 差分对比
difffolded.pl
效果满意?
✅ 优化完成