性能分析经验
下面这份“性能分析进阶速查清单”力求覆盖 CPU-bound、内存/缓存-bound、I/O-bound、并发 等常见瓶颈场景,兼顾 方法论+工具+硬件背景知识。你已经有两年基础,以下侧重高阶要点和易被忽视的细节;哪怕是老鸟,也可以作为随手翻的“备忘口袋书”。
0 · 方法论先行:三个黄金原则
-
先测量,后修改
- “感觉慢” ≠ “真正热点”;每一次改动都要有 前后对比。
-
只改关键 1%
- 经典 80/20:程序 5 % 的代码通常耗掉 95 % 的时间。
-
一次改一个变量
- 同步记录 githash、编译选项、硬件环境,避免结论漂移。
1 · 底层硬件指标速览
维度 | 常见瓶颈 | 典型硬件计数器 / 指标 |
---|---|---|
指令执行 | 前端卡顿、解码欠饱和 | idq_uops_not_delivered* , icache_misses |
分支预测 | 预测失败、间接跳转 | branch-misses , bp_late_mispred |
流水线停顿 | 结构冲突、寄存器重命名不足 | cycle_activity.stalls_total , uops_issued.any |
缓存层级 | L1/L2/TLB/LLC | L1-dcache-load-misses , L2_RQSTS.* , dTLB-load-misses |
内存带宽 | DRAM 饱和 | mem_load_uops_retired.* , UNC_M_CAS_COUNT |
NUMA | 远端访问 | numa_miss , numa_foreign |
并发锁 | 自旋、队列冲突 | sched:sched_switch , cpu-clock + off-CPU flame graph |
I/O | syscalls、磁盘/网卡等待 | biotime , iotime , eBPF block:block_rq_issue/complete |
上下文切换 | 过度线程化 | context-switches , sched:sched_stat* |
记忆法:指标可分 取指(Frontend) ➜ 执行(Backend) ➜ 数据(Memory) 三大链路;链路越靠后,单次代价更高。
2 · 工具矩阵(Linux / macOS / Windows)
需求 | CLI | GUI / Web | 说明 |
---|---|---|---|
系统级采样 | perf , perf c2c , ebpf_exporter |
hotspot, Speedscope | 基于硬件 PMC,最低噪声 |
微观指令级 | Intel vtune -collect uarch-exploration AMD μProf |
VTune GUI / Web | 看 IPC、port utilization |
低开销火焰图 | perf script + FlameGraph |
Speedscope | 最常用热点定位 |
锁/调度 | perf sched , perf lock , off-CPU fg |
Trace Compass | 定位自旋、线程睡眠 |
内存分配 | jemalloc heap prof , libc malloc_stats , heaptrack |
massif-viz | 判定碎片、泄漏 |
C/C++ San | asan , tsan , msan |
IDE plugin | “正确性→性能” 顺序永远没错 |
eBPF 全家桶 | bcc , bpftrace , perf_event_open |
bpfdoor, Inspektor | 生产环境热点排查神器 |
语言特定 | go tool pprof , py-spy , rbspy , node --prof |
pprof web, Speedscope | 把 flamegraph 养成肌肉记忆 |
I/O 与网络 | iostat , fio , tcpdump + wireshark |
netdata, Grafana | I/O 就两问:带宽够吗?延迟在哪? |
组合拳:
perf record -e cycles:u -j any,u -- <app>
→perf script
→stackcollapse-perf.pl | flamegraph.pl > out.svg
→ 浏览器打开。
养成“不出 flame 图不改代码”的习惯。
3 · 性能瓶颈识别套路
-
CPU 利用率高?
-
是 → 看 IPC、分支 mispredict、port 利用率
-
否 → 大概率 → I/O / lock / 内存带宽 / NUMA
-
-
Hotspot 在用户态还是内核态?
--call-graph dwarf,fp lbr
区分 -
L1/L2 命中率好但 L3 坏? 关注 结构体布局、false sharing
-
TLB miss 飙高? ➜ 分页失配,检查 大页、数据局部性
-
context-switch 爆炸? ➜ 过度线程化 / 线程争锁 / 频繁 syscalls
-
Off-CPU 时间多? ➜ I/O wait、mutex wait、GC stop-the-world
-
NUMA 远程访问多? ➜ 绑核 / 内存亲和;或采用 per-NUMA shard
-
IOPS vs 带宽:SSD/网卡 性能 = 小包延迟 + _大包吞吐_二元函数
-
测评环境可重复:关闭睿频、关虚拟机抢占、设定 CPU 亲和、独占网卡
4 · 典型高级问题 & “老司机招”
症状 | 快速定位 | 常见补救 |
---|---|---|
Branch miss >5 % | vtune uarch view → 分支面板 |
将 if-else 改成表驱动;手动 hint __builtin_expect |
TLB miss 高 | perf stat -e dTLB-load-misses |
开 Transparent HugePages,或 madvise(MADV_HUGEPAGE) |
Port 0/1 饱和 | vtune port analysis |
重排指令、SIMD 并行、调 GCC -mtune |
False sharing | perf c2c , vtune false-sharing |
按 cache line 对齐 alignas(64) ;每线程 padding |
锁竞争 | perf lock record |
换无锁队列;或使用 MCS/QSpin |
Page-fault 短暂峰值 | perf trace -e page-faults |
预分配;将大对象放 mmap 区 |
Off-CPU >40 % | off-CPU flame graph | 合并系统调用;启用 io_uring / AIO |
5 · 测量规范与坑
-
Warm-up:JIT / Cache / Branch predictor 预热,常用 discard 前 N 次结果
-
固定时钟源:不同 CPU P-state / 睿频会污染基准;可用
taskset
+ BIOS 固定频率 -
统计显著性:用 95 % CI;不要只看单次 run
-
调试符号:
-fno-omit-frame-pointer -g
可以打开;但正式 run 替换成 minimal symbols -
Sampling ≠ Tracing:采样开销低、丢事件;跟踪 100 % 捕获但贵。先采样,再必要时跟踪。
-
Benchmark ≠ Production:离线跑分 ≠ 真·线上;线上需要 eBPF/Perf/USDT “黑盒”采样。
-
Profile-Guided Optimization:LLVM
-fprofile-{instr,generate,use}
,GCC-fprofile-use
;收数据时输入必须贴近真实负载。
6 · 必读资料 & 社区
-
Brendan Gregg – “Systems Performance (2e)”、brendangregg.com
-
Agner Fog – CPU microarchitecture & optimization PDFs
-
Intel® 64/IA-32 SDM 卷 3B 章节 “Performance-Monitoring Events”
-
LLVM “AutoFDO / BOLT” 博客、GCC AutoFDO patches
-
Talks:CPPCon “Cpp Performance Engineering” (Faisal Vali)、Google “BPF Internals”
☑️ 如何使用这张清单
-
遇到卡顿 → 查对照表 找对应计数器
-
用合适的工具 抓 1 ~ 2 分钟采样 → 出火焰图
-
比对 80 / 20:确定 Top N 函数 / 系统调用
-
改代码 / 配置 → 再跑一次;直到收益递减明显
-
写总结 / 记笔记:把新坑挖进你的“直觉数据库”
愿这张“性能宝典”帮你把每一次 profiling 都练成“升级打怪”。
如果你想针对某一类瓶颈(如 NUMA 或 eBPF tracing)深入做练习,随时告诉我,我可以帮你设计一份 hands-on 练习脚本或提供示例项目。祝调优愉快!