Google Benchmark: 从入门到高手

wuzhiguocarterwuzhiguocarter
2 min read

Google Benchmark 是一个用于 C++ 的微基准测试库,帮助开发者测量代码片段的性能。通过这个库,您可以分析代码在不同输入、数据规模和系统配置下的表现。

一、入门使用

安装

您可以通过包管理器或从源代码构建安装 Google Benchmark:

  1. 使用 vcpkg 安装:

     vcpkg install benchmark
    
  2. 从源码构建

     git clone https://github.com/google/benchmark.git
     cd benchmark
     cmake -E make_directory "build"
     cmake -E chdir "build" cmake ..
     cmake --build "build" --config Release
     sudo cmake --build "build" --config Release --target install
    

基本使用

  1. 包含头文件

     #include <benchmark/benchmark.h>
    
  2. 定义基准测试函数

     static void BM_Function(benchmark::State& state) {
         for (auto _ : state) {
             // 在此处放置您要基准测试的代码
         }
     }
     BENCHMARK(BM_Function);
    

    for (auto _ : state) 循环中,基准测试函数会多次运行,以获取可靠的测量值。

  3. 运行基准测试: 在主函数中使用 BENCHMARK_MAIN(); 宏来运行所有定义的基准测试。

     BENCHMARK_MAIN();
    

自定义基准测试

  1. 传递参数: 您可以使用 ArgsRange 方法传递不同的参数,测试不同的输入规模或条件。

     BENCHMARK(BM_Function)->Arg(8)->Arg(16);
     BENCHMARK(BM_Function)->Range(8, 8<<10);
    
  2. 设置时间单位: 默认情况下,Google Benchmark 使用 ns 作为时间单位。您可以通过 Unit 方法更改为其他单位,例如 ms

     BENCHMARK(BM_Function)->Unit(benchmark::kMillisecond);
    
  3. 多线程测试: 使用 Threads 方法指定线程数量,以测试多线程环境下的性能。

     BENCHMARK(BM_Function)->Threads(2);
    
  4. 报告内存使用: 使用 MeasureProcessMemory 方法可以报告基准测试过程中内存的使用情况。

     BENCHMARK(BM_Function)->MeasureProcessMemory();
    

运行与输出

编译并运行基准测试代码,Google Benchmark 将自动处理计时和结果报告。输出结果包括基准测试名称、迭代次数和每次迭代的时间(例如 ns/opus/op)。

示例

以下是一个简单的示例,测量创建一个空 std::string 对象所需的时间:

#include <benchmark/benchmark.h>

static void BM_StringCreation(benchmark::State& state) {
    for (auto _ : state) {
        std::string empty_string;
    }
}
BENCHMARK(BM_StringCreation);

BENCHMARK_MAIN();

在这个例子中,BM_StringCreation 测量了创建空字符串的时间。您可以通过 BENCHMARK_MAIN() 来运行这个基准测试。

二、进阶使用

基准测试的caes如何注入?

在 Google Benchmark 中,您可以通过多种方式将测试用例(cases)注入到基准测试函数中,以测试代码在不同条件下的性能。这些用例通常是通过函数参数或通过 Google Benchmark 提供的专用 API 注入的。以下是几种常见的方法:

1. 使用 Args 注入单个参数

如果您的基准测试需要单个参数,您可以使用 Args 方法为每个用例注入不同的值:

static void BM_Function(benchmark::State& state) {
    int param = state.range(0);  // 读取参数
    for (auto _ : state) {
        // 使用参数 param 进行基准测试
    }
}
BENCHMARK(BM_Function)->Arg(1)->Arg(2)->Arg(3);

在这个例子中,BM_Function 函数会分别使用 1、2、3 作为参数进行测试。

2. 使用 RangeRanges 注入一系列参数

Range 允许您为一个参数指定范围,而 Ranges 允许您为多个参数指定范围。Google Benchmark 会自动生成参数组合并运行测试。

// 使用单个参数范围
BENCHMARK(BM_Function)->Range(8, 8<<10);

// 使用多个参数范围
BENCHMARK(BM_Function)->Ranges({{8, 8<<10}, {1, 4}});

在这段代码中,第一个 Range 测试了 8 到 8<<10 范围内的参数。第二个 Ranges 测试了两个参数的所有组合。

3. 使用 ArgsProduct 注入参数的笛卡尔积

ArgsProduct 可以生成多个参数的笛卡尔积,以覆盖所有可能的参数组合。

static void BM_Function(benchmark::State& state) {
    int param1 = state.range(0);
    int param2 = state.range(1);
    for (auto _ : state) {
        // 使用 param1 和 param2 进行基准测试
    }
}

BENCHMARK(BM_Function)->ArgsProduct({{1, 2, 3}, {4, 5}});

在这个例子中,BM_Function 将被分别调用 6 次,每次组合来自 param1 的 [1, 2, 3] 和 param2 的 [4, 5]。

4. 使用 Complexity 注入动态生成的测试用例

如果您的测试用例生成逻辑更为复杂,可以通过动态生成测试用例的方式注入:

static void BM_Function(benchmark::State& state) {
    for (auto _ : state) {
        // 根据输入动态生成测试用例
        std::vector<int> data(state.range(0), 42);
        // 测试逻辑
    }
}

BENCHMARK(BM_Function)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity();

在这个例子中,BM_Function 会基于输入动态生成一个大小为 state.range(0) 的向量进行测试。

5. 使用 Fixture 为复杂的测试用例设置

对于更复杂的设置,您可以使用 BenchmarkFixture 类,允许在每次测试之前设置和清理数据。

class MyBenchmark : public benchmark::Fixture {
public:
    void SetUp(const ::benchmark::State& state) override {
        // 设置代码
    }

    void TearDown(const ::benchmark::State& state) override {
        // 清理代码
    }
};

BENCHMARK_DEFINE_F(MyBenchmark, BM_Test)(benchmark::State& state) {
    for (auto _ : state) {
        // 基准测试逻辑
    }
}

BENCHMARK_REGISTER_F(MyBenchmark, BM_Test)->Args({8});

这个例子中,SetUpTearDown 会在每次基准测试前后被调用,从而可以设置和清理测试用例。

总结

通过这些方法,您可以灵活地将不同的测试用例注入到 Google Benchmark 的基准测试函数中,以测试不同的输入、配置和场景下代码的性能。

附录

benchmark user guide

0
Subscribe to my newsletter

Read articles from wuzhiguocarter directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

wuzhiguocarter
wuzhiguocarter

I am a senior software engineer working on DiDi.