はじめに
C++のcontainerライブラリの自作に取り組んでいます。
ライブラリを自作したら、スタンダードライブラリとの比較をしたいと思って、いろいろ調べていたら、Google Benchmarkというツールに出会いました。
Google Testと同様に、簡単に書くことが出来て、多機能です。
インストール
こちらのREADMEを見れば丁寧に書いてあるので、そのままやるだけなのですが、一応こちらでも書いておきます。
cmakeが入っていない人はbrewで入れましょう。
brew install cmake
以下の手順を実行します。
# クローンします
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
実行方法
インストールができたら、実行ファイルを書きましょう。
#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
実行
$ ./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の説明
今、自分が使っているコードで説明します。
#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つが出力されます。
実行環境情報
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のいいところです。
---------------------------------------------------------------
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で実行速度比較を出して、より良いコードを書けるよう勉強していこうと思います。