google/benchmark の導入から簡単な使い方まで


## 目的

[google/Benchmark](https://github.com/google/benchmark?tab=readme-ov-file) の導入、簡単な使い方を説明する。
概ねリポジトリに記載のある内容だが、 `README.md` も十分に親切というわけではないため、理解を深めるためにもこちらで自分なりにまとめる。

### 参考文献

リポジトリに含まれるドキュメントを参照すれば十分利用できる。またヘッダファイルにも多くのコメントが含まれている。

- [User Guide (google/Benchmark)](https://github.com/google/benchmark/blob/main/docs/user_guide.md)
- その他のドキュメントは [/docs (google/Benchmark)](https://github.com/google/benchmark/tree/main/docs) に格納されている。
- 機能の全容を調べるにはヘッダファイル [/include/benchmark/benchmark.h](https://github.com/google/benchmark/blob/main/include/benchmark/benchmark.h)　を参照する。

### 開発環境

以下の環境で動作確認を行った。

- Ubuntu 22.04 LTS
- CMake 3.27.0
- gcc 13.1.0
- google/benchmark v1.8.3

cf. [Requirements (google/Benchmark)](https://github.com/google/benchmark?tab=readme-ov-file#requirements)

## Google Benchmark とは

> A library to benchmark code snippets, similar to unit tests.

ベンチマークを作成するためのライブラリ。（ヘッダオンリーではない。）
C++03 以降で利用可能だが、C++11 以降とでは使い方が一部異なる。以降も C++11 以上の環境を前提として説明する。

Valgrind などのプロファイリングツールと異なり既存プログラムの内部詳細を解析することはできない。主に関数を対象として、複数の実行設定（パラメタの組み合わせ）を比較するベンチマークプログラムの作成を補助するライブラリである。

[google/googletest](https://github.com/google/googletest/tree/main) とは近い設計スタイルで、 Benchmark の実装にも使用されているが、併用の必要性やシナジーは特にない。

## 導入

リポジトリに記載が無かったが、 `FetchContent` を使用して導入できる。
ビルドオプションは [/CMakeLists.txt (google/Benchmark)](https://github.com/google/benchmark/blob/main/CMakeLists.txt) を参照した。以下では時間がかかるユニットテストを特に無効にしている。

```cmake:FetchContent
# disable building the unit tests
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE)

include(FetchContent)
FetchContent_Declare(
  gbench
  URL https://github.com/google/benchmark/archive/refs/tags/v1.8.3.zip
      DOWNLOAD_EXTRACT_TIMESTAMP OFF)
FetchContent_MakeAvailable(gbench)
```

正常にビルドできた場合、 後は以下のように executable にリンクするだけで使用できる。

```cmake:linking benchmark
target_link_libraries(${YOUR_TARGET_NAME} benchmark::benchmark)
```

## 使い方

### Minimum Example

- ヘッダは `benchmark/benchmark.h` と間接的に `benchmark/export.h` をインクルードする。
- 任意の関数を直接登録できるわけではなく、 `benchmark::State` オブジェクトを引数に取る関数でラップする必要がある。
- `BENCHMARK` マクロで関数をベンチマークとして登録する。同じ関数をパラメタを変えて複数回登録できるが、 後述の `Benchmark::Name` でベンチマーク名を分ける必要がある。
- `BENCHMARK_MAIN` マクロは `main` 関数を定義する。ユーザーで `main` 関数を定義することも可能。ただし戻り値 0 を想定するため、 GoogleTest と併用できない。

```cpp:minimum_example
#include <benchmark/benchmark.h> // namespace benchmark

void TargetFunction() {} // benchmark target

// define the benchmark function with benchmark::State argument
static void BM_TargetFunction(benchmark::State &state) {
  for (auto _ : state)
    TargetFunction();
}
// Register the function as a benchmark
BENCHMARK(BM_TargetFunction);
// expand to main function
BENCHMARK_MAIN();
```

ビルドした実行ファイルを実行すると、以下のような結果がコンソールに出力される。（出力形式は変更可能。）

```shell
2024-05-01T10:00:00+00:00
Running ./MinimumExample
Run on (12 X 2932 MHz CPU s)
CPU Caches:
  L1 Data 48 KiB (x6)
  L1 Instruction 32 KiB (x6)
  L2 Unified 512 KiB (x6)
  L3 Unified 12288 KiB (x1)
Load Average: 0.07, 0.06, 0.07
----------------------------------------------------------
Benchmark                Time             CPU   Iterations
----------------------------------------------------------
BM_SomeFunction      0.929 ns        0.929 ns    737031406
```

## オプション機能

主要な機能は User Guide か /docs フォルダ下の他ドキュメントに記載がある。
ここでは特に、ベンチマークにパラメタを渡す方法と、実行オプションについて紹介する。

### Passing parameters to `Benchmark`

`BENCHMARK` マクロの戻り値で渡される `benchmark::Benchmark` オブジェクトポインタからメソッドを呼び出すことで、ベンチマークごとに様々なパラメタを設定できる。

後述の実行オプションと重複する機能もある。重複して設定された場合、基本的に `Benchmark` で設定された値が優先される。

#### Usage

`BENCHMARK` マクロの戻り値にメソッドチェーンを追加していく。
意味的に矛盾がない限り、メソッドは任意の順で呼び出せる。

```cpp:UserGuide記載の例を一部修正
static void BM_DenseRange(benchmark::State &state) {
  const auto array_size = static_cast<uint64_t>(state.range(0));
  const auto elem_default = static_cast<int32_t>(state.range(0));
  for (auto _ : state) {
    std::vector<int32_t> v(array_size, elem_default);
    auto data = v.data();
    benchmark::DoNotOptimize(data);
    benchmark::ClobberMemory();
  }
}

BENCHMARK(BM_DenseRange)
    ->Name("benchmark/DenseRange")
    ->DenseRange(0, 1024, 128); // [ 0, 128, 256, ... , 1024 ]
```

```shell:実行結果例
2024-05-01T10:00:00+00:00
Running ./BasicExample
Run on (12 X 2932 MHz CPU s)
CPU Caches:
  L1 Data 48 KiB (x6)
  L1 Instruction 32 KiB (x6)
  L2 Unified 512 KiB (x6)
  L3 Unified 12288 KiB (x1)
Load Average: 0.07, 0.06, 0.07
--------------------------------------------------------------------
Benchmark                          Time             CPU   Iterations
--------------------------------------------------------------------
benchmark/DenseRange/0         0.233 ns        0.233 ns   1000000000
benchmark/DenseRange/128        34.2 ns         34.2 ns     20892872
benchmark/DenseRange/256        52.4 ns         52.4 ns     13404492
benchmark/DenseRange/384        73.2 ns         73.2 ns      9362482
benchmark/DenseRange/512        89.4 ns         89.4 ns      7761504
benchmark/DenseRange/640         113 ns          113 ns      6481608
benchmark/DenseRange/768         123 ns          123 ns      5699625
benchmark/DenseRange/896         139 ns          139 ns      4988107
benchmark/DenseRange/1024        157 ns          157 ns      4245010
```

#### メソッド一覧

- "Arg", "Range" を含むメソッドは、ベンチマークにパラメタを渡すためのもの。
- その他 Setup/Teardown や、測定時間、スレッド関係の機能などがある。

```cpp:benchmark.hよりメソッド抜粋

class BENCHMARK_EXPORT Benchmark {
  /* ... */

  Benchmark* Name(const std::string& name);
  Benchmark* Arg(int64_t x);
  Benchmark* Unit(TimeUnit unit);
  Benchmark* Range(int64_t start, int64_t limit);
  Benchmark* DenseRange(int64_t start, int64_t limit, int step = 1);
  Benchmark* Args(const std::vector<int64_t>& args);
  Benchmark* ArgPair(int64_t x, int64_t y) /* ... */;
  Benchmark* Ranges(const std::vector<std::pair<int64_t, int64_t> >& ranges);
  Benchmark* ArgsProduct(const std::vector<std::vector<int64_t> >& arglists);
  Benchmark* ArgName(const std::string& name);
  Benchmark* ArgNames(const std::vector<std::string>& names);
  Benchmark* Setup(void (*setup)(const benchmark::State&));
  Benchmark* Teardown(void (*teardown)(const benchmark::State&));
  Benchmark* Apply(void (*func)(Benchmark* benchmark));
  Benchmark* RangeMultiplier(int multiplier);
  Benchmark* MinTime(double t);
  Benchmark* MinWarmUpTime(double t);
  Benchmark* Iterations(IterationCount n);
  Benchmark* Repetitions(int n);
  Benchmark* DisplayAggregatesOnly(bool value = true);
  Benchmark* MeasureProcessCPUTime();
  Benchmark* UseRealTime();
  Benchmark* UseManualTime();
  Benchmark* Complexity(BigO complexity = benchmark::oAuto);
  Benchmark* Complexity(BigOFunc* complexity);
  Benchmark* ComputeStatistics(const std::string& name,
                               StatisticsFunc* statistics,
                               StatisticUnit unit = kTime);
  Benchmark* Threads(int t);
  Benchmark* ThreadRange(int min_threads, int max_threads);
  Benchmark* DenseThreadRange(int min_threads, int max_threads, int stride = 1);
  Benchmark* ThreadPerCpu();

  /* ... */
};
```

### 実行オプション

`BENCHMARK_MAIN` マクロで展開される `main` 関数を持つベンチマークプログラムには実行オプションを指定できる。
実行オプションは出力テキストの形式に関するものが主で、その他のベンチマークの実行を制御するオプションとで構成される。
例えば `--benchmark_out=<filename>` および `--benchmark_out_format=json` オプションを指定することで、ベンチマーク結果を JSON 形式でファイルに出力できる。

#### 実行オプション一覧

実行オプションの一覧は実行ファイルに `--help` (not `-h`) オプションを付けて実行することで確認できる。

```shell
benchmark [--benchmark_list_tests={true|false}]
          [--benchmark_filter=<regex>]
          [--benchmark_min_time=`<integer>x` OR `<float>s` ]
          [--benchmark_min_warmup_time=<min_warmup_time>]
          [--benchmark_repetitions=<num_repetitions>]
          [--benchmark_enable_random_interleaving={true|false}]
          [--benchmark_report_aggregates_only={true|false}]
          [--benchmark_display_aggregates_only={true|false}]
          [--benchmark_format=<console|json|csv>]
          [--benchmark_out=<filename>]
          [--benchmark_out_format=<json|console|csv>]
          [--benchmark_color={auto|true|false}]
          [--benchmark_counters_tabular={true|false}]
          [--benchmark_perf_counters=<counter>,...]
          [--benchmark_context=<key>=<value>,...]
          [--benchmark_time_unit={ns|us|ms|s}]
          [--v=<verbosity>]
```