C++ 专家(24):C++ 性能优化实战——性能分析、优化技巧、常见模式
更新时间:2026-09-01。本文是
languages/cpp/expert/专家层第 24 篇,接 C++ vs Rust。C++ 性能优化的正确姿势:先 profiling,再优化,不做盲目的优化。本文总结了常见的性能瓶颈和优化技巧,包括缓存命中率、分支预测、内联、分配器、循环展开等。
本文要回答的问题
- 怎么找到性能瓶颈?用什么工具?
- 缓存命中率为什么重要?怎么提升缓存命中率?
- 分支预测怎么优化?为什么有序数据更快?
- 内联和函数开销怎么权衡?什么时候该 inline?
- 内存分配怎么优化?
一、先 Profiling,再优化
bash
# Linux perf 采样
perf record ./your_program
perf report
# 火焰图可视化
git clone https://github.com/brendangregg/FlameGraph
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > flame.svg
# macOS instruments
instruments -t "Time Profiler" ./your_program
# Windows Visual Studio
# 用诊断工具 -> 性能探查器规则: 没有 profiling 就不优化。大部分代码不瓶颈,优化错地方只会让代码更复杂。
二、缓存命中率优化
空间局部性
cpp
// 二维数组遍历:行优先 vs 列优先
// ✅ 行优先:相邻元素在同一行,缓存命中率高
int sum_row_first(const std::vector<std::vector<int>>& mat) {
int sum = 0;
for (size_t i = 0; i < mat.size(); i++) {
for (size_t j = 0; j < mat[i].size(); j++) {
sum += mat[i][j];
}
}
return sum;
}
// ❌ 列优先:每次跨一行,缓存不命中,慢几倍
int sum_col_first(const std::vector<std::vector<int>>& mat) {
int sum = 0;
for (size_t j = 0; j < mat[0].size(); j++) {
for (size_t i = 0; i < mat.size(); i++) {
sum += mat[i][j];
}
}
return sum;
}数据对齐和结构体紧凑布局
cpp
// 结构体大小按对齐,尽量让成员按类型大小排列
// 把小成员放在一起,减少 padding
// ❌ 坏布局(浪费空间)
struct Bad {
char a;
double b; // 7 字节 padding
char c;
int d; // 4 字节 padding
}; // 总大小:1+7+8+1+3+4 = 24 字节
// ✅ 好布局(减少 padding)
struct Good {
double b; // 8 字节
int d; // 4 字节
char a; // 1 字节
char c; // 1 字节
}; // 总大小:8+4+1+1 = 14 → 对齐到 16 字节三、分支预测优化
cpp
// 有序数据:分支预测器更容易预测
// 找出大于 0 的元素
// 慢:如果数组无序,分支经常预测错
int sum_br(const std::vector<int>& v) {
int sum = 0;
for (int x : v) {
if (x > 0) { // 无序 → 分支预测错很多
sum += x;
}
}
return sum;
}
// 快:用条件移动,无分支
int sum_nobranch(const std::vector<int>& v) {
int sum = 0;
for (int x : v) {
sum += x * (x > 0); // 编译器生成无分支指令
}
return sum;
}
// 排序后,有序数组分支预测准确率接近 100%
std::sort(v.begin(), v.end());
// 然后分支预测就准了四、内联和函数调用
cpp
// 小函数:inline 消除调用开销,编译器更容易优化
inline int add(int a, int b) {
return a + b;
}
// 大函数:inline 会导致代码膨胀,不适合 inline
// 经验:函数体小于 10 行适合 inline
// 经验法则:
// ✅ 小函数、lambda → 内联
// ❌ 大函数、递归函数 → 不要强制 inline
// C++17:inline 变量
inline int global_value = 42;五、内存分配优化
cpp
// 频繁分配小对象:用内存池或 PMR
// 不要每次分配都走 new/delete
// 每次 new/delete
// 慢:频繁分配产生碎片
// 用内存池分配
std::pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));
std::pmr::vector<int> vec(&pool); // 所有分配在 buffer 里
// 好处:分配一次整块内存,不需要每次分配,非常快六、循环展开
cpp
// 循环展开:减少循环次数,提升指令流水线利用率
// 对小循环效果明显
// 原始:每次处理 1 个元素
for (size_t i = 0; i < n; i++) {
sum += vec[i];
}
// 循环展开:每次处理 4 个元素
size_t i = 0;
for (; i + 4 <= n; i += 4) {
sum += vec[i] + vec[i+1] + vec[i+2] + vec[i+3];
}
// 处理剩余元素
for (; i < n; i++) {
sum += vec[i];
}
// 现代编译器自动循环展开,不需要手动做,但知道原理很重要七、常见性能瓶颈对照表
| 瓶颈 | 现象 | 对策 |
|---|---|---|
| 缓存 miss | CPU cache 命中率低 | 行优先遍历,紧凑数据结构 |
| 分支 mispredict | 分支预测错误率高 | 排序,用无分支代码 |
| 堆分配频繁 | 每次分配走 new | 内存池,PMR,预分配 |
| 函数调用开销 | 大量小函数调用 | inline |
| 复制过多 | 不必要的拷贝 | 移动语义,emplace_back,pass-by-reference |
八、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 盲优化 | 代码变复杂但性能提升不明显 | 先 profiling,确定瓶颈再优化 |
| 过度内联 | 代码膨胀,缓存指令 miss | 只 inline 小函数 |
| 忽略缓存 | 缓存命中率太低 | 布局优化,数据对齐 |
| 过早优化 | 一开始就追求极致性能 | 先正确,再快,对正确的瓶颈优化 |
相关与延伸
C++ 专家层 24 篇完成。接下来开始 Backend 系列:后端开发 第一章 HTTP 协议。
一句话总结
C++ 性能优化实战:先 profiling(perf + 火焰图)找到瓶颈,再优化,不盲优化;缓存命中率是最大性能因素,行优先遍历,紧凑结构体布局减少 padding;分支预测错误会导致性能下降,有序数据更快,无分支更快;小函数 inline 消除调用开销,大函数 inline 导致代码膨胀;频繁小对象分配用内存池减少分配次数;现代编译器自动循环展开,不需要手动做;不要过早优化,先正确再快。