性能优化的第一步不是“直接改代码”,而是先确认瓶颈在哪里、瓶颈是否稳定复现,以及优化后如何验证收益。
先明确优化目标
在开始优化前,先回答三个问题:
- 优化目标是什么:吞吐、延迟、CPU、内存,还是磁盘/网络开销。
- 问题出现在哪个场景:压测、线上高峰、冷启动,还是特定请求路径。
- 优化是否可量化:是否有 QPS、TP99、CPU 使用率、内存占用等基线数据。
没有基线数据时,优化很容易变成“感觉更快了”,最后无法证明收益。
先定位热点,再决定手段
1. 编译器与构建层面
常见手段包括:
- 打开合适的编译优化等级,如
-O2或-O3 - 在合适场景下评估 LTO、BOLT 等链接/布局优化能力
- 确认是否打开了调试选项、额外日志或 sanitizer,避免把调试开销误判为业务瓶颈
这类优化适合在代码路径已经稳定、二进制规模和启动方式明确时评估。
2. 代码路径本身
常见性能问题通常集中在以下几类:
- I/O 过多:磁盘、网络、频繁 flush
- 锁竞争:线程争用、过粗粒度锁、原子变量滥用
- 内存分配:频繁分配释放、对象过大、缓存 miss
- 线程切换:线程数过多、任务过细、上下文切换频繁
- 数据结构选择不当:例如高频
rehash、不必要的拷贝、冷热数据混放、伪共享
优化时优先处理热点路径上的高频操作,而不是平均分散地“到处微调”。
3. 用工具确认热点函数
- 用户态 CPU 热点:
perf record/perf report - 堆内存热点:结合
heap profiler、jemalloc或tcmalloc的分析能力 - 系统层面:
pidstat、mpstat、vmstat、iostat
先确认瓶颈是在用户态、内核态、锁等待还是 I/O 等待,再决定是否改代码、改参数或者改部署方式。
常见优化方向
减少不必要的数据搬运
- 优先减少不必要的 copy
- 合理使用
std::move - 避免在热点路径上构造大对象或频繁做格式化
降低竞争与同步开销
- 缩小锁粒度
- 让串行路径改为并行时先确认共享数据边界
- 能异步的地方尽量异步,但要控制队列长度和回压机制
控制瞬时资源抖动
很多性能问题不是平均负载高,而是瞬时尖峰触发:
- 瞬时流量高峰
- GC 或后台任务集中执行
vector扩容、unordered_maprehash- 突发磁盘读导致 major page fault
这类问题要重点看峰值期间的监控,而不是只看平均值。
评估硬件和部署因素
如果热点已经明确且代码优化空间有限,再考虑:
- 提升单机硬件能力
- 调整 CPU 亲和性(cpu affinity)
- 做水平扩展或拆分服务
这一步应建立在前面已经确认瓶颈的前提下,否则只是把问题推迟暴露。
一个实用的排查顺序
- 先确定现象:慢在哪里,影响范围多大。
- 采集基线:延迟、吞吐、CPU、内存、I/O。
- 用 profiling 工具确认热点函数和热点路径。
- 判断瓶颈属于计算、锁、I/O、内存还是调度。
- 只修改最主要的瓶颈点,并在同样场景下复测。
- 对比优化前后数据,确认收益和副作用。
总结
性能优化的关键不是“知道很多技巧”,而是建立一条稳定的方法链:
- 先测量
- 再定位
- 后优化
- 最后复盘
这样才能避免把时间花在非瓶颈位置上。
FEATURED TAGS
Git
Cheat Sheet
Markdown
Tools
C++
Linker
Thread
Linux
TCP
Network
GDB
Debug
leetcode
链表
WSL
Ubuntu
Windows
Linux Kernel
GCC
Android
adb
Troubleshooting
Profiling
Sanitizer
glibc
MySQL
Database
Python
curl
Build
ELF
clang-format
CMake
Graphviz
Performance
vcpkg
Protobuf
排查
速查
内存
STL
调试
性能分析
性能
读书笔记
方法论
架构
网络
Timer
mbedTLS
TLS
安全
负载均衡
脚本
工具
LRU
二叉树
BST
中序遍历
回溯
二分查找
优先队列
排序
旋转数组
jenkins
部署