第35讲:性能测试——把Google Benchmark请进CMake项目
性能测试这件事,说实话,很多C++开发者一开始都不太重视。我早年带团队的时候,就吃过这个亏——代码写完了,功能全对,一上线发现跑得比蜗牛还慢。后来我养成了一个习惯:每个核心模块,必须挂上性能测试。
今天咱们聊的,就是怎么把Google Benchmark这个性能测试框架,优雅地集成到CMake项目里。说白了,就是让CMake帮你自动下载、编译、链接Benchmark,你只管写测试用例就行。
为什么选Google Benchmark?
市面上性能测试工具不少,但Google Benchmark有几个让我特别舒服的地方:
- 开箱即用:API设计得很干净,几行代码就能跑起来
- 统计信息丰富:均值、中位数、标准差、最大最小值,全给你列出来
- 防止编译器优化:这个太重要了!我见过太多人测了个寂寞,就是因为编译器把空循环优化掉了
- CMake集成友好:有官方的Find模块,也有FetchContent方案
整体思路
咱们先看看今天要搭建的结构:
嗯,整体流程不复杂。核心就三步:配置CMake → 下载Benchmark → 写测试用例。咱们一步步来。
第一步:主CMakeLists.txt怎么写
我个人习惯用 FetchContent 来拉取Google Benchmark。为什么不用系统包管理器?因为不同机器上版本不一致,容易出幺蛾子。FetchContent能保证所有人用同一个版本,省心。
cmake_minimum_required(VERSION 3.14)
project(MyBenchmarkProject LANGUAGES CXX)
# 开启测试
enable_testing()
# 使用 FetchContent 下载 Google Benchmark
include(FetchContent)
FetchContent_Declare(
googlebenchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG v1.8.3
GIT_SHALLOW TRUE
)
# 注意:Benchmark 默认会编译测试,我们不需要
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googlebenchmark)
# 添加我们的性能测试子目录
add_subdirectory(benchmark)
GIT_SHALLOW TRUE 可以只拉取最新一次提交,省带宽省时间。我第一次用的时候没加这个,等了好久才下载完。
第二步:编写性能测试源文件
在项目根目录下新建 benchmark/ 文件夹,里面放两个文件:CMakeLists.txt 和 sort_benchmark.cpp。
先看 benchmark/CMakeLists.txt:
# 生成性能测试可执行文件
add_executable(sort_benchmark sort_benchmark.cpp)
# 链接 Google Benchmark 库
target_link_libraries(sort_benchmark
PRIVATE
benchmark::benchmark
benchmark::benchmark_main
)
# 注册为 CTest 测试
add_test(NAME sort_benchmark COMMAND sort_benchmark)
这里有个细节:benchmark::benchmark_main 这个库自带 main() 函数,你就不用自己写了。省一行是一行嘛。
再看 sort_benchmark.cpp:
#include <benchmark/benchmark.h>
#include <vector>
#include <algorithm>
#include <random>
// 生成随机数据
std::vector<int> generate_data(size_t size) {
std::vector<int> data(size);
std::mt19937 rng(42);
std::uniform_int_distribution<int> dist(0, 1000000);
for (auto& x : data) x = dist(rng);
return data;
}
// 测试 std::sort
static void BM_SortStd(benchmark::State& state) {
auto data = generate_data(state.range(0));
for (auto _ : state) {
auto copy = data; // 每次重新拷贝,避免排序后的数据影响
std::sort(copy.begin(), copy.end());
benchmark::DoNotOptimize(copy);
}
}
BENCHMARK(BM_SortStd)->Arg(1000)->Arg(10000)->Arg(100000);
// 测试 std::stable_sort
static void BM_StableSort(benchmark::State& state) {
auto data = generate_data(state.range(0));
for (auto _ : state) {
auto copy = data;
std::stable_sort(copy.begin(), copy.end());
benchmark::DoNotOptimize(copy);
}
}
BENCHMARK(BM_StableSort)->Arg(1000)->Arg(10000)->Arg(100000);
BENCHMARK_MAIN();
benchmark::benchmark_main,就不要在源文件里写 BENCHMARK_MAIN() 了,否则会重复定义 main 函数。我刚开始就踩过这个坑,编译报错找了半天。
第三步:编译与运行
配置和编译跟普通CMake项目一样:
mkdir build && cd build
cmake ..
cmake --build . --parallel
ctest --output-on-failure -R sort_benchmark
或者直接运行可执行文件:
./benchmark/sort_benchmark
你会看到类似这样的输出:
--------------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------------
BM_SortStd/1000 45.2 us 45.1 us 15423
BM_SortStd/10000 542 us 541 us 1294
BM_SortStd/100000 6541 us 6538 us 107
BM_StableSort/1000 52.3 us 52.1 us 13456
BM_StableSort/10000 621 us 619 us 1123
BM_StableSort/100000 7890 us 7885 us 89
你看,std::sort 比 std::stable_sort 快一些,数据量越大差距越明显。这就是性能测试的价值——用数据说话,而不是靠猜。
进阶技巧:自定义参数与报告
有时候你想测不同数据分布下的性能,比如有序数据、逆序数据、重复数据。可以用 ArgsProduct 做组合测试:
BENCHMARK(BM_SortStd)
->ArgsProduct({
benchmark::CreateRange(1000, 100000, 10), // 数据量
{0, 1, 2} // 0:随机, 1:有序, 2:逆序
});
另外,我个人习惯把结果导出成JSON,方便后续分析:
./benchmark/sort_benchmark --benchmark_format=json > result.json
然后用Python或者Excel画图,一目了然。
避坑指南
最后分享几个我踩过的坑:
- 编译器优化:一定要用
benchmark::DoNotOptimize和benchmark::ClobberMemory,否则编译器可能把整个循环优化掉,测出来的时间接近0 - 多线程干扰:跑性能测试时,关掉其他占用CPU的程序。我有一回开着Chrome跑测试,结果波动特别大
- 预热问题:Google Benchmark默认会做预热,但如果你测的是冷启动场景,记得用
->MinWarmUpTime(0)关掉预热 - CMake版本:FetchContent需要CMake 3.14+,老版本请升级
核心要点回顾:
- 用 FetchContent 管理 Google Benchmark 依赖,保证版本一致
- 链接
benchmark::benchmark_main可以省去自己写 main 函数 - 使用
DoNotOptimize防止编译器优化掉被测代码 - 通过
Arg()或ArgsProduct()测试不同数据规模 - 结果导出JSON,方便后续分析和可视化
好了,这一讲的内容就到这里。性能测试是个长期习惯,不是一次性任务。把Google Benchmark集成到CMake项目里,以后每次改代码都能顺手跑一下,心里踏实。