先度量再优化
性能优化第一条铁律:用数据定位瓶颈,别凭直觉。盲目优化既浪费时间又可能让代码变复杂还更慢。
通常 20% 的代码消耗 80% 的时间。profiler 帮你精准找到那 20%,把精力用对地方。
缓存与局部性
CPU 缓存远快于内存。连续访问(vector/数组)命中缓存;链表、散乱指针导致缓存未命中,性能骤降。
// 友好:连续内存顺序访问
for (const auto& x : vec) sum += x;
// 不友好:链表跳跃访问,缓存未命中多
for (auto it = list.begin(); it != list.end(); ++it) sum += *it;若只用到部分字段,SoA(结构体数组)更缓存友好、利于向量化;AoS(数组结构体)则更直观,按访问模式取舍。
move 语义
C++11 的右值引用与 std::move 允许"移动"而非拷贝资源(如字符串、容器底层缓冲),把昂贵复制变成指针交换。
std::vector<int> make() {
std::vector<int> v(1'000'000);
return v; // 具名对象 NRVO/移动,不拷贝
}
std::vector<int> a = make();
std::vector<int> b = std::move(a); // a 被掏空,b 接管缓冲被 std::move 后的对象处于有效但未指定状态,只能赋值/析构,别再读取其值。
避免拷贝
传递大对象用 const T&;返回大对象靠返回值优化(RVO/NRVO);用 reserve 避免容器反复扩容。
void process(const BigData& d); // 不拷贝
std::string build() {
std::string s;
s.reserve(1024); // 避免多次分配
return s; // RVO 移动
}向量化 SIMD
编译器常自动向量化循环(一次处理多个数据)。编写"简单、无分支、连续访问"的循环更利于自动 SIMD;必要时用 #pragma omp simd 或 intrinsics。
// 简单无依赖循环,编译器易向量化
for (size_t i = 0; i < n; ++i)
c[i] = a[i] + b[i];循环里别调用有副作用/虚函数、别有跨迭代依赖,否则编译器不敢向量化。
Profiling
用 perf(Linux)、Instruments(macOS)、VTune 做采样/插桩分析,看热点函数与缓存命中率,定位真正瓶颈。
perf record ./app
perf report # 交互式查看热点微观基准
用 Google Benchmark 写微基准,注意避免"死代码消除"(把结果 print 出来),并在稳定环境下多次取中位数。
static void BM_Sum(benchmark::State& s) {
for (auto _ : s) {
volatile int x = compute(); // 防止被优化掉
}
}
BENCHMARK(BM_Sum);常见陷阱与 FAQ
可能新算法缓存不友好或分支多,抵消了渐进复杂度的优势。对小规模数据,常数因子与局部性往往更关键——用基准验证。
仅当对象拥有昂贵资源(堆缓冲)时移动才划算。对 int 等小对象,移动等同拷贝,无收益。
受 CPU 频率调节、后台进程、缓存 warm-up 影响。固定 CPU 频率、预热、多次取中位数,关闭无关进程。