性能基准测试:建立基线,选对工具
说到性能调优,我最常被问的问题是:“优化前和优化后到底快了多少?”
如果连这个都说不清楚,那调优就变成了拍脑袋。
所以,第二件事就是——建立性能基线。
说白了,就是给当前代码拍一张“快照”。之后每次改动,跟这张照片比一比,提升还是退步,一目了然。
为什么需要性能基线?
我几年前接手过一个 SDK 模块。上线前总觉得慢,但没数据。
后来硬着头皮跑了十遍测试,记下每遍耗时,取中位数。
从那时起,我就养成了习惯:任何调优,必须先拍照。
基线的作用就三点:
- 量化起点,知道“现在有多慢”
- 验证优化,判断“改完真快了”
- 防止回退,避免“改了这个坏了那个”
基线建立四步法
- 选一个有代表性的场景 —— 别在空循环上测,要测真实数据流。
- 固定环境 —— CPU 频率、内存、负载、工具版本全锁死。
- 跑足够多次 —— 我个人习惯至少 20 次,取中位数或 P95。
- 记录指标 —— 不止耗时,还要看内存分配、GC 次数、吞吐量。
嗯,这里要注意:基线不是一成不变的。
SDK 升级、依赖库变动、测试机更换,都得重新拍一张。
常用工具:BenchmarkDotNet vs Google Benchmark
你现在主攻的是 .NET 还是 C++?
.NET 阵营就用 BenchmarkDotNet,C++ 就用 Google Benchmark。
两个都是工业级工具,我两边都用过。
| 特性 | BenchmarkDotNet (.NET) | Google Benchmark (C++) |
|---|---|---|
| 预热机制 | 内置,自动跳过 JIT 抖动 | 手动或自动,支持多次预热 |
| 统计输出 | 中位数、标准差、P95 等 | 均值、中位数、最小值等 |
| 内存诊断 | 托管内存分配、GC 次数 | 需结合 perf 或自定义 |
| 跨平台 | .NET 生态,Linux/Windows/macOS | 原生 C++,全平台 |
| 上手难度 | 低,加个特性就行 | 中等,需要 CMake 或构建 |
BenchmarkDotNet 示例
[MemoryDiagnoser]
public class MyBenchmark
{
[Benchmark]
public void OldWay()
{
// 旧实现
}
[Benchmark]
public void NewWay()
{
// 新实现
}
}
// 控制台启动:BenchmarkRunner.Run<MyBenchmark>();
这个框架会自动处理预热、迭代、冷启动。我一般跑完直接把结果导出成 markdown 贴到 PR 里。
Google Benchmark 示例
#include <benchmark/benchmark.h>
static void BM_OldWay(benchmark::State& state) {
for (auto _ : state) {
// 旧实现
}
}
BENCHMARK(BM_OldWay);
static void BM_NewWay(benchmark::State& state) {
for (auto _ : state) {
// 新实现
}
}
BENCHMARK(BM_NewWay);
BENCHMARK_MAIN();
我个人觉得 Google Benchmark 的优缺点都很明显:
跑起来快,但要自己管内存。曾经有次漏了预热,结果前 3 次迭代慢 30%,把我吓了一跳。
用例设计原则——避坑指南
我以前有个同事,设计了一个“空循环”基准测试,结果优化 100 倍。
实际上在生产代码里,那个循环从来不会被单独执行。这种基线就是废的。
设计测试用例时,我遵循这几个原则:
- 贴近真实:输入数据的大小、分布、缓存命中概率尽量和线上对齐。
- 隔离外部依赖:别把数据库、网络、IO 混进来,除非你要测的就是它们。
- 排除 JIT/预热影响:跑前一定要充分预热,直到耗时稳定。
- 多维度指标:不光看耗时,还要看内存分配、吞吐量、尾延迟。
Stopwatch 手动计时,结果把框架的预热机制覆盖了。后来发现前后两次跑的结果波动 50%。想哭。
还有一个容易被忽略的:不要只看中位数。
如果你的 SDK 是实时音视频类的,P99 延迟更重要。
本章核心流程:基线建立到结果对比
下面这张图把整个过程串起来了——从定义场景到输出报告。
从一次真实压测说起
有个项目做图像处理 SDK,我一开始用 BenchmarkDotNet 跑了两组对比。
第一组没加 [MemoryDiagnoser],结果只看到耗时优化 15%。
加上后才发现,内存分配减少了 80%,GC 停顿直接从 120ms 降到 8ms。
这才是真正的收益。
你想想看,光看耗时,你可能错过最关键的 GC 优化。
所以,我的建议是:
基线不只是“快了多少”,而是“好在哪”。
选对工具,做对用例,记录全数据——这三把斧头砍下去,任何性能问题都别想逃。