Google benchmark使用手册及范例

劳英华
2023-12-01

构建集成

在robotic_arm的third_party已经集成了benchmark,只需在相关测试用例代码的CmakeLists.txt添加如下内容:

target_link_libraries(xxx PRIVATE benchmark pthread)

Demo样例

存在以下模式样例:

1. 使用BENCHMARK、BENCHMARK_MAIN宏

#include <benchmark/benchmark.h>
#include <chrono>
#include <thread>

void BM_DemoSleep(benchmark::State& state) {
  for (auto _ : state){
    //待测试的代码
  }
}
BENCHMARK(BM_DemoSleep); // 注册要测试的函数对象

BENCHMARK_MAIN(); // main函数,运行benchmark初始化和执行

2. 直接使用Benchmark相应的接口

#include <benchmark/benchmark.h>
#include <chrono>
#include <thread>

void BM_DemoSleep(benchmark::State& state) {
  for (auto _ : state){
    std::this_thread::sleep_for(std::chrono::nanoseconds(1000)); //待测试的代码
  }
}

void BM_DemoSleep1(benchmark::State& state, int id) {
  std::cout << "id:"<< id << std::endl;
  for (auto _ : state){
    std::this_thread::sleep_for(std::chrono::nanoseconds(1000));
  }
}

int main(int argc, char** argv) {
  ::benchmark::Initialize(&argc, argv); // 初始化Benchmark
  if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1;
  
  // 使用函数指针注册
  ::benchmark::RegisterBenchmark("BM_DemoSleep", &BM_DemoSleep);
  // 使用Lamba函数注册
  ::benchmark::RegisterBenchmark("BM_DemoSleep1", [](benchmark::State& state){
    for (auto _ : state){
      std::this_thread::sleep_for(std::chrono::nanoseconds(1000));
    }
  });
  
  // 使用带参数的函数指针注册
  int id = 10;
  ::benchmark::RegisterBenchmark("BM_DemoSleep2", &BM_DemoSleep1, id);
  
  ::benchmark::RunSpecifiedBenchmarks(); // 运行
  ::benchmark::Shutdown(); 
}

3. 使用Fixture

class BMDemo : public benchmark::Fixture {
public:
  void SetUp(const benchmark::State& state) {
    id_ = 2;
  }

  void TearDown(const ::benchmark::State& state) {
    id_ = 0;
  }

  int GetId() const {return id_;};
private:
  int id_{0};
};

BENCHMARK_F(BMDemo, Test0)(benchmark::State& state) {
  for (auto _ : state) {
     std::this_thread::sleep_for(std::chrono::milliseconds(GetId())); // test code
  }
}
BENCHMARK_F(BMDemo, Test1)(benchmark::State& state) {
  for (auto _ : state) {
     std::this_thread::sleep_for(std::chrono::milliseconds(GetId())); // test code
  }
}

原理:BENCHMARK_F(BMDemo, Test0)(benchmark::State& state){},会创建一个BMDemo_Test0_Benchmark的类,继承至BMDemo,然后实现BMDemo_Test0_Benchmark::BenchmarkCase(benchmark::State& state){}成员函数;在Fixture的Run方法中会一次调用SetUp->BenchmarkCase->TearDown

配置参数

1. Arg参数

接口名称作用
Benchmark* Arg(int64_t x);向Benchmark对象的std::vector<std::vector<int64_t> > args_添加一个元素(元素为vector{x})
Benchmark* Range(int64_t start, int64_t limit);Benchmark* Range(int64_t start, int64_t limit);
Benchmark* DenseRange(int64_t start, int64_t limit, int step = 1);Benchmark* DenseRange(int64_t start, int64_t limit, int step = 1);
Benchmark* Args(const std::vector<int64_t>& args);向Benchmark对象的std::vector<std::vector<int64_t> > args_添加一个元素(元素为args)
Benchmark* ArgPair(int64_t x, int64_t y)向Benchmark对象的std::vector<std::vector<int64_t> > args_添加一个元素(元素为vector{x,y})
void BM_Arg(benchmark::State& state) {
  std::cout << "arg1:" << state.range(0) << "\n"; // state.range(0)获取参数
  for (auto _ : state) {
     std::this_thread::sleep_for(std::chrono::milliseconds(state.range(0)));
  }
}

int main(int argc, char** argv) {
  ::benchmark::Initialize(&argc, argv);
  if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1;
  ::benchmark::RegisterBenchmark("BM_Arg", &BM_Arg)->Arg(10); // ->Arg()设置单个参数
    ::benchmark::RegisterBenchmark("BM_Arg", &BM_Arg)->Arg(10)->Arg(11); //分别以10,11参数运行BM_Arg
  ::benchmark::RunSpecifiedBenchmarks();
  ::benchmark::Shutdown(); 
}

2. 测试多少次(iterations)

会有如下策略(简单的将如果没有明确设置iteration,会使用相应的计算方法(会使用到min_time),递增迭代次数然后以最后一个确定的结果report):
我们可能逐渐增加基准的长度(迭代次数),直到我们确定结果是重要的。 一旦我们这样做了,我们就会报告最后的结果并退出。 请注意,如果有重复,则迭代计数仅为第一次重复计算,其他重复仅使用该预先计算的迭代计数。

::benchmark::RegisterBenchmark("BM_Arg", &BM_Arg)->Iterations(10); // 迭代执行10次,也就是for(auto _ : state){}循环会迭代10次

::benchmark::RegisterBenchmark("BM_Arg", &BM_Arg); // 会按照计算规则,迭代n次

3. 重复多少次(Repetitions)

指的是整个函数对象调用多少次,默认值是1

::benchmark::RegisterBenchmark("BM_Arg", &BM_Arg)->Iterations(10)->Repetitions(3); // 迭代执行10次,也就是for(auto _ : state){}循环会迭代10次; 重复调用3次

4. 显示时间单位

Benchmark* Unit(TimeUnit unit);
设置显示时间单位:
kNanosecond, kMicrosecond, kMillisecond, kSecond.

5. 多线程

Benchmark* Threads(int t);设置多少个线程运行测试(线程函数中运行注册的函数对象)
Benchmark* ThreadRange(int min_threads, int max_threads);类似args_, 以min_threads为起点,倍率为2,终点为 max_threads,向thread_counts_添加元素比如: Range(1, 16), Threads(1)->Threads(2)-> Threads(4)-> Threads(8)-> Threads(16),分别以1、2、4、8、16个线程进行测试
Benchmark* DenseThreadRange(int min_threads, int max_threads, int stride = 1);类似args_, 以min_threads为起点,步长为1,终点为 max_threads,向thread_counts_添加元素,DenseThreadRange(1, 8, 3), 1、4、7、8个线程进行测试

6. 时间类型

  void BM_Arg(benchmark::State& state) {
      for (auto _ : state) {
        auto start = std::chrono::high_resolution_clock::now();
        // Simulate some useful workload with a sleep
        std::this_thread::sleep_for(sleep_duration);
        auto end = std::chrono::high_resolution_clock::now();
    
        auto elapsed_seconds =
          std::chrono::duration_cast<std::chrono::duration<double>>(
            end - start);
    
        state.SetIterationTime(elapsed_seconds.count());
      }
  }
  ::benchmark::RegisterBenchmark("BM_Arg", &BM_Arg)->UseManualTime();

7. 统计分析结果

会统计每次的结果,然后输出分析结果:
mean: 平均值、median: 中值、stddev: 标准差、cv:标准差/平均值
自定义分析结果,比如最小值,最大值
接口:

  typedef double(StatisticsFunc)(const std::vector<double>&);
  Benchmark* ComputeStatistics(std::string name, StatisticsFunc* statistics,
                               StatisticUnit unit = kTime);   

示例:

::benchmark::RegisterBenchmark("BM_Arg", &BM_Arg)->Iterations(10)->Repetitions(10)->Unit(benchmark::kMillisecond)
  ->ComputeStatistics("max", [](const std::vector<double>& v)->double{
    return *std::max_element(v.begin(), v.end());
  }, benchmark::kTime)
  ->ComputeStatistics("min", [](const std::vector<double>& v)->double{
    return *std::min_element(v.begin(), v.end());
  }, benchmark::kTime);

命令行

–benchmark_out_format=<console|json|csv>
定义输出格式
–benchmark_out=
定义文件名
–benchmark_filter=
定义过滤规则(正则表达式)
–benchmark_repetitions=n
定义重复次数
–benchmark_report_aggregates_only={true|false}
上报内容是否只上报聚合内容(省略每次repetition的内容)
–benchmark_display_aggregates_only={true|false}
屏幕输出内容是否只上报聚合内容(省略每次repetition的内容)

其他

模版方法和模版Fixture本文无介绍,相关使用方法可以参考google benchmark的github地址

 类似资料: