GoogleBenchmark用法
目录
- 介绍
- 构建安装
- 项目集成
- 注意
- 基本用法
- 示例代码
- 代码解析
- 编译运行
- 高级功能
- 参数化测试
- 控制时间测量
- 多线程测试
- 性能计数器
- 自定义输出
- 避免优化干扰
- 自定义统计
- 过滤测试
- 实践
- 常见问题
- 参考资源
介绍
Google Benchmark 是一个功能强大的 C++ 微基准测试库,用于精确测量代码的性能。它由 Google 开发和维护,提供了丰富的功能来支持各种基准测试场景,从简单函数的性能测量到复杂的参数化测试。它通过自动调整迭代次数以确保统计稳定性,提供了高精度的性能数据。相比手动计时,Google Benchmark 具有以下优势:
- 自动化迭代:动态确定运行次数以获得稳定的统计结果。
- 多种时间测量:支持 CPU 时间、墙钟时间(Wall Clock Time)以及进程总 CPU 时间。
- 灵活的参数化:支持参数化测试,方便测试不同输入规模或配置。
- 多线程支持:能够处理多线程环境下的性能测量。
- 输出格式多样:支持控制台、JSON 和 CSV 输出,便于分析和可视化。
- 集成性强:与 Google Test 集成良好,易于嵌入现有项目。
构建安装
-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON
:自动下载 Google Test 依赖。
-DCMAKE_BUILD_TYPE=Release
:以 Release 模式构建,优化性能。
如果不需要测试,可以添加 -DBENCHMARK_ENABLE_GTEST_TESTS=OFF
。
项目集成
在自己的项目中,可以通过 CMake 集成 Google Benchmark:
- 使用find_package
find_package(benchmark REQUIRED)
target_link_libraries(your_target benchmark::benchmark benchmark::benchmark_main)
- 使用 add_subdirectory: 将 Google Benchmark 作为子目录添加到项目:
add_subdirectory(benchmark)
target_link_libraries(your_target benchmark::benchmark benchmark::benchmark_main)
注意
在 GCC 下,必须链接 pthread 库(-lpthread),否则可能出现运行时异常。
如果使用硬件性能计数器(如 libpfm),需要确保系统支持并配置 kernel.perf_event_paranoid(Linux 特有):
sudo sh -c 'echo kernel.perf_event_paranoid=-1 >> /etc/sysctl.conf'
sudo sysctl -p
基本用法
Google Benchmark 的核心是定义基准测试函数并通过宏注册到框架中。
示例代码
#include <benchmark/benchmark.h>// 定义基准测试函数
static void BM_StringCreation(benchmark::State& state) {for (auto _ : state) {std::string empty_string;}
}// 注册基准测试
BENCHMARK(BM_StringCreation);// 另一个基准测试
static void BM_StringCopy(benchmark::State& state) {std::string x = "hello";for (auto _ : state) {std::string copy(x);}
}
BENCHMARK(BM_StringCopy);// 程序入口
BENCHMARK_MAIN();
代码解析
- 基准测试函数
- 必须接受一个 benchmark::State& 参数,用于控制测试状态。
- 使用 for (auto _ : state) 循环,框架会自动控制迭代次数。
- 测试代码放在循环体内,框架会测量其执行时间。
- 注册基准测试:使用 BENCHMARK 宏将函数注册为基准测试。
- 程序入口:BENCHMARK_MAIN() 定义主函数,初始化框架并运行所有注册的基准测试。
编译运行
输出示例(控制台格式):
------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------
BM_StringCreation 12 ns 12 ns 10000000
BM_StringCopy 25 ns 25 ns 5000000
输出解释:
Time:墙钟时间(Wall Clock Time),单位为纳秒(ns)。
CPU:主线程的 CPU 时间(默认)。
Iterations:测试运行的迭代次数,框架自动调整以确保结果稳定。
高级功能
Google Benchmark 提供了许多高级功能,支持更复杂的测试场景。
参数化测试
参数化测试允许测试不同输入规模或配置。以下是几种参数传递方式:
- 使用 Arg,为基准测试指定单个参数值:
static void BM_Memcpy(benchmark::State& state) {char* src = new char[state.range(0)];char* dst = new char[state.range(0)];memset(src, 'x', state.range(0));for (auto _ : state) {memcpy(dst, src, state.range(0));}state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(state.range(0)));delete[] src;delete[] dst;
}
BENCHMARK(BM_Memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1024);
- 使用 Range
- 指定参数范围,框架自动生成参数(默认以 8 倍递增):
BENCHMARK(BM_Memcpy)->Range(8, 8<<10); // 从 8 到 8192
- 自定义递增倍数:
BENCHMARK(BM_Memcpy)->RangeMultiplier(2)->Range(8, 8<<10); // 以 2 倍递增
- 使用 Args,支持多参数测试:
static void BM_SetInsert(benchmark::State& state) {std::set<int> data;for (auto _ : state) {state.PauseTiming(); // 暂停计时以排除初始化影响data = ConstructRandomSet(state.range(0));state.ResumeTiming();for (int j = 0; j < state.range(1); ++j) {data.insert(RandomNumber());}}
}
BENCHMARK(BM_SetInsert)->Args({1<<10, 1})->Args({1<<10, 8})->Args({8<<10, 1});
- 使用 Ranges,指定多参数范围,测试所有组合:
BENCHMARK(BM_SetInsert)->Ranges({{1<<10, 8<<10}, {1, 512}});
- 自定义参数,使用 Apply 自定义参数组合:
static void CustomArguments(benchmark::internal::Benchmark* b) {for (int i = 0; i <= 10; ++i) {for (int j = 0; j <= 50; ++j) {b->Args({i, j});}}
}
BENCHMARK(BM_SetInsert)->Apply(CustomArguments);
控制时间测量
Google Benchmark 默认测量主线程的 CPU 时间,但支持以下选项:
- 墙钟时间:使用 UseRealTime() 测量实际时间:
BENCHMARK(BM_test)->UseRealTime();
- 进程总 CPU 时间:测量所有线程的 CPU 时间:
BENCHMARK(BM_test)->MeasureProcessCPUTime();
多线程测试
- 支持多线程基准测试:
static void BM_MultiThread(benchmark::State& state) {for (auto _ : state) {// 多线程代码}
}
BENCHMARK(BM_MultiThread)->Threads(4); // 使用 4 个线程
- 自定义线程模型(例如使用 OpenMP):
static void BM_OpenMP(benchmark::State& state) {for (auto _ : state) {#pragma omp parallel forfor (int i = 0; i < state.range(0); i++) {// 工作负载}}
}
BENCHMARK(BM_OpenMP)->Range(8, 8<<10);
性能计数器
在 Linux 上,可以使用 libpfm 收集硬件性能计数器(如 CPU 周期、指令数):
BENCHMARK(BM_test)->UseRealTime()->PerfCounters({"CYCLES", "INSTRUCTIONS"});
需要确保 libpfm 已安装,并配置 CMake 时启用 -DBENCHMARK_ENABLE_LIBPFM=ON。
自定义输出
支持多种输出格式:
- 控制台:默认,可读,带颜色。
- JSON:
./benchmark_example --benchmark_format=json
- CSV:
./benchmark_example --benchmark_format=csv
设置输出文件:
./benchmark_example --benchmark_out=output.json --benchmark_out_format=json
避免优化干扰
编译器优化可能导致代码被优化掉,影响基准测试结果。使用以下方法防止优化:
- benchmark::DoNotOptimize:防止编译器优化掉结果:
for (auto _ : state) {int result = ComputeSomething();benchmark::DoNotOptimize(result);
}
- benchmark::ClobberMemory:确保内存写入生效:
for (auto _ : state) {ComputeAndStore();benchmark::ClobberMemory();
}
自定义统计
设置处理的数据量或项目数:
state.SetBytesProcessed(int64_t(state.iterations()) * state.range(0));
state.SetItemsProcessed(state.iterations());
过滤测试
- 使用 --benchmark_filter 运行特定测试:
./benchmark_example --benchmark_filter=BM_StringCreation
- 正则表达式支持:
./benchmark_example --benchmark_filter=BM_String.*
实践
- 优化级别:
- 始终以 Release 模式(-O3)编译基准测试,避免 Debug 模式导致不准确的结果。
- 检查编译器优化是否移除关键代码,使用 DoNotOptimize 或 ClobberMemory。
- 统计稳定性:Google Benchmark 自动调整迭代次数以确保结果稳定,但仍需多次运行以验证一致性。
- 多线程测试:
- 多线程测试可能受系统调度影响,确保测试环境稳定(如固定 CPU 核心)。
- 使用 taskset(Linux)控制 CPU 核心分配。
- 避免副作用:确保基准测试代码不依赖全局状态或外部资源(如文件、网络),否则可能引入噪声。
- 分析结果:
- 使用 JSON 输出结合脚本(如 Python 的 compare.py)分析性能差异。
- 关注 Time 和 CPU 的差异,可能反映 I/O 或系统调度的影响。
- 硬件性能计数器:在 Linux 上使用 libpfm 获取更多性能指标(如缓存命中率),但需要管理员权限。
常见问题
- 如何避免编译器优化干扰?
使用 benchmark::DoNotOptimize 和 benchmark::ClobberMemory。例如:
int result = Compute();
benchmark::DoNotOptimize(result);
benchmark::ClobberMemory();
- 如何比较两个版本的性能?
运行两个版本的基准测试,保存 JSON 输出,然后使用 compare.py:
./benchmark_old --benchmark_out=old.json --benchmark_out_format=json
./benchmark_new --benchmark_out=new.json --benchmark_out_format=jsonpython3 compare.py old.json new.json
- 如何测试 GPU 代码?
使用手动计时:
static void BM_GPU(benchmark::State& state) {for (auto _ : state) {state.PauseTiming();// 准备 GPU 数据state.ResumeTiming();// 执行 GPU 计算}
}
BENCHMARK(BM_GPU)->UseManualTime();
- 如何在 CI 中集成?
使用 Bencher 等工具,结合 JSON 输出,在 CI 管道中运行并分析结果:
bencher run --adapter cpp_google "./benchmark_example --benchmark_format=json"
参考资源
- 官方文档:https://github.com/google/benchmark/blob/main/docs/user_guide.md
- GitHub 仓库:https://github.com/google/benchmark
- 讨论组:https://groups.google.com/g/benchmark-discuss
- 性能计数器:https://github.com/google/benchmark/blob/main/docs/perf_counters.md
- 博客教程:https://www.bencher.dev/learn/benchmarking/cpp/google-benchmark/