C/C++パフォーマンスチューニング　Google Benchmarkに入門しよう！

# はじめに

C++のcontainerライブラリの自作に取り組んでいます。
ライブラリを自作したら、スタンダードライブラリとの比較をしたいと思って、いろいろ調べていたら、**Google Benchmark**というツールに出会いました。

**Google Test**と同様に、簡単に書くことが出来て、多機能です。

## インストール

https://github.com/google/benchmark

こちらのREADMEを見れば丁寧に書いてあるので、そのままやるだけなのですが、一応こちらでも書いておきます。

cmakeが入っていない人はbrewで入れましょう。

```sh
brew install cmake
```

以下の手順を実行します。

```sh
# クローンします
git clone https://github.com/google/benchmark.git

# ライブラリのルートディレクトリに移動します
cd benchmark

# ビルドディレクトリを作成して、ビルド出力を配置します。
cmake -E make_directory "build"

# 依存関係を持つシステムファイルを作成し、依存関係をダウンロードします。
cmake -E chdir "build" cmake -DBENCHMARK_DOWNLOAD_DEPENDENCIES=on -DCMAKE_BUILD_TYPE=Release ../

# ライブラリをbuildします
cmake --build "build" --config Release
```

## 実行方法

インストールができたら、実行ファイルを書きましょう。

```cpp
#include <benchmark/benchmark.h>

static void BM_StringCreation(benchmark::State& state) {
  for (auto _ : state) {
    std::string empty_string;
  }
}
// Register the function as a benchmark
BENCHMARK(BM_StringCreation);

// Define another benchmark
static void BM_StringCopy(benchmark::State& state) {
  std::string x = "hello";
  for (auto _ : state) {
    std::string copy(x);
  }
}
BENCHMARK(BM_StringCopy);

BENCHMARK_MAIN();
```

### コンパイル
書いたファイルを巻き込んでコンパイルします。

```
g++ mybenchmark.cc -std=c++11 -isystem benchmark/include \
  -Lbenchmark/build/src -lbenchmark -lpthread -o mybenchmark
```

### 実行

```sh
$ ./mybenchmark
2022-02-05T21:36:18+09:00
Running ./benchmark
Run on (8 X 2200 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x4)
  L1 Instruction 32 KiB (x4)
  L2 Unified 256 KiB (x4)
  L3 Unified 6144 KiB (x1)
Load Average: 2.75, 3.06, 2.87
------------------------------------------------------------
Benchmark                  Time             CPU   Iterations
------------------------------------------------------------
BM_StringCreation       34.5 ns         34.1 ns     20878446
BM_StringCopy           8.39 ns         8.19 ns     84670932
```

Benchmarkが出ました！

## Syntaxの説明

今、自分が使っているコードで説明します。

```cpp
#include <benchmark/benchmark.h>
#include <vector>
#include "vector.hpp"

static void BM_FtVector(benchmark::State& state) {
  // Benchmarkでは測らない前処理をここで書いておきます。
  ft::vector<int> v;

  // このfor文の中にBenchmarkを測りたい処理を書きます。
  for (auto _ : state) {
    for (int i = 0; i < state.range(0); ++i) {
      v.push_back(i);
    }
  }
}
// Benchmarkを実行するには、このマクロを書きます。
// Range(1, 1 << 22)で、state.range(0)に段階的に渡す数を指定できます。
BENCHMARK(BM_FtVector)->Range(1, 1 << 22);

static void BM_StdVector(benchmark::State& state) {
  std::vector<int> v;
  for (auto _ : state) {
    for (int i = 0; i < state.range(0); ++i) {
      v.push_back(i);
    }
  }
}
BENCHMARK(BM_StdVector)->Range(1, 1 << 22);

// main関数
BENCHMARK_MAIN();
```

## Benchmarkの見方

Benchmarkの出力結果は**実行環境情報**と、実行結果の詳細の2つが出力されます。

### 実行環境情報

```sh
2022-02-05T21:42:00+09:00
Running ./benchmark
Run on (8 X 2200 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x4)
  L1 Instruction 32 KiB (x4)
  L2 Unified 256 KiB (x4)
  L3 Unified 6144 KiB (x1)
Load Average: 2.62, 3.19, 3.03
```

### 実行結果

- Benchmark名/渡した引数
- 時間
- CPU時間
- 反復回数

反復回数が記載されているので、どれくらいの平均が出されているのか、ひと目で分かることもGoogle Benchmarkのいいところです。

```sh
---------------------------------------------------------------
Benchmark                     Time             CPU   Iterations
---------------------------------------------------------------
BM_FtVector/1              71.0 ns         65.7 ns     11932361
BM_FtVector/8               416 ns          403 ns      1784617
BM_FtVector/64             2881 ns         2856 ns       238887
BM_FtVector/512           22778 ns        22487 ns        30598
BM_FtVector/4096         181550 ns       180412 ns         3816
BM_FtVector/32768       1444000 ns      1434107 ns          460
BM_FtVector/262144     12002002 ns     11827379 ns           58
BM_FtVector/2097152    91833606 ns     91457000 ns            6
BM_FtVector/4194304   183728092 ns    182849500 ns            4
BM_StdVector/1             70.6 ns         70.1 ns     11003867
BM_StdVector/8              460 ns          457 ns      1598152
BM_StdVector/64            3484 ns         3468 ns       201855
BM_StdVector/512          28056 ns        27935 ns        23835
BM_StdVector/4096        219552 ns       218309 ns         3385
BM_StdVector/32768      1769702 ns      1760995 ns          400
BM_StdVector/262144    14391678 ns     14313447 ns           47
BM_StdVector/2097152  110284984 ns    109849714 ns            7
BM_StdVector/4194304  227442098 ns    226656333 ns            3
```

## まとめ

試していませんが、他にも様々な機能があるようです。

- スループットの表示
- メモリのBenchmark
- 出力形式でcsv, jsonを指定できる
- スレッド数の指定
- 最適化の抑制
- 環境設定の上書き

よく仲間内で、俺のコードが最強だ！という話で盛り上がっていますが、計測が適当な部分があったので、**Google Benchmark**で実行速度比較を出して、より良いコードを書けるよう勉強していこうと思います。