第35讲:性能测试——把Google Benchmark请进CMake项目

性能测试这件事,说实话,很多C++开发者一开始都不太重视。我早年带团队的时候,就吃过这个亏——代码写完了,功能全对,一上线发现跑得比蜗牛还慢。后来我养成了一个习惯:每个核心模块,必须挂上性能测试

今天咱们聊的,就是怎么把Google Benchmark这个性能测试框架,优雅地集成到CMake项目里。说白了,就是让CMake帮你自动下载、编译、链接Benchmark,你只管写测试用例就行。

为什么选Google Benchmark?

市面上性能测试工具不少,但Google Benchmark有几个让我特别舒服的地方:

  • 开箱即用:API设计得很干净,几行代码就能跑起来
  • 统计信息丰富:均值、中位数、标准差、最大最小值,全给你列出来
  • 防止编译器优化:这个太重要了!我见过太多人测了个寂寞,就是因为编译器把空循环优化掉了
  • CMake集成友好:有官方的Find模块,也有FetchContent方案

整体思路

咱们先看看今天要搭建的结构:

Google Benchmark 集成到 CMake 项目 CMake 项目根目录 CMakeLists.txt(主配置) FetchContent 下载 Benchmark benchmark/ 测试源文件 enable_testing() GIT_REPOSITORY 指定仓库 BENCHMARK() 宏 ✅ 性能测试可执行文件

嗯,整体流程不复杂。核心就三步:配置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.txtsort_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::sortstd::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::DoNotOptimizebenchmark::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项目里,以后每次改代码都能顺手跑一下,心里踏实。


公众号:蓝海资料掘金营,微信 deep3321