- org-mode & google/benchmark
- main関数を書かずに短く記載
きっかけ#
競プロや普段の業務でコードを書く場合のスピード比較をする際、 C++
では std::chrono::high_resolution_clock::now()
1 で計測したい関数を囲い、処理時間を出力するなどしていた。
時間の計測はできるが複数処理を別々に計測したい場合は、
std::chrono::high_resolution_clock::now()
まみれになったり- 平均を取るために何回もFor文で囲んだり、、、
本来の目的以外のコードが増えてしまうのが悩みだった。
そこで最近、 google/benchmark: A microbenchmark support library というものを知った 2。 Google謹製だし、使ってみるかと少し確認するコードを書いてみると、これが使いやすい。
自分は小さいコードの単位で試したい場合や試行メモを残して置きたい場合は、 Emacs
の org-mode
で org-babel
というコード実行機能を使うことが多い3, 4。
簡単にベンチマークが実行できるようになるので、 セットアップのメモとテンプレートを残す。
org-babel で google/benchmark をリンクする#
インストール#
brew install google-benchmark
サンプルコードを実行#
benchmark/README.md からサンプルコードをコピペ。
#include <benchmark/benchmark.h>
するために、 #+HEADER:
と :flags
を用いてライブラリをリンク。
org-babel
のオプションは こちら によくまとまっています。
#+HEADER: :includes <benchmark/benchmark.h>
#+begin_src C++ :flags "-std=c++14 $(pkg-config --cflags --libs benchmark) -pthread"
コード本体
#+end_src
時間がかかると有名なアッカーマン関数5 を用いて、ベンチマークを実施。
次のSRCブロックを評価(C-c C-c
)すると,エラーが出る。
#+HEADER: :includes <benchmark/benchmark.h>
#+begin_src C++ :flags "-std=c++14 $(pkg-config --cflags --libs benchmark) -pthread"
#include <benchmark/benchmark.h>
int ackernamm(int m, int n) {
if (m == 0) {
return n + 1;
} else if (n == 0) {
return ackernamm(m - 1, 1);
} else {
return ackernamm(m - 1, ackernamm(m, n - 1));
}
}
static void BM_SomeFunction(benchmark::State& state) {
// Perform setup here
for (auto _ : state) {
ackernamm(0, 0);
}
}
// Register the function as a benchmark
BENCHMARK(BM_SomeFunction);
// Run the benchmark
BENCHMARK_MAIN();
#+end_src
1つ目のエラー#
Macの場合は以下のようなエラーが出る。 Clangを使用するように強制してあげることで解決した。
Undefined symbols for architecture arm64:
"benchmark::internal::Benchmark::Benchmark(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)", referenced from:
benchmark::internal::FunctionBenchmark::FunctionBenchmark(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, void (*)(benchmark::State&)) in ccE6nhmb.o
"std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::find(char, unsigned long) const", referenced from:
benchmark::StrSplit(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, char) in libbenchmark.a[18](string_util.cc.o)
benchmark::StrSplit(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, char) in libbenchmark.a[18](string_util.cc.o)
"std::__1::basic_stringbuf<char, std::__1::char_traits<char>, std::__1::allocator<char>>::str() const", referenced
NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
ld: symbol(s) not found for architecture arm64
collect2: error: ld returned 1 exit status
[ Babel evaluation exited with code 1 ]
(when (string= system-type "darwin")
(setq org-babel-C++-compiler "clang++")
)
2つ目のエラー#
***.cpp:11:54: error: function definition is not allowed here
static void BM_SomeFunction(benchmark::State& state) {
^
***.cpp:19:11: error: use of undeclared identifier 'BM_SomeFunction'
BENCHMARK(BM_SomeFunction);
^
***.cpp:21:1: error: function definition is not allowed here
BENCHMARK_MAIN();
^
/opt/homebrew/Cellar/google-benchmark/1.8.4/include/benchmark/benchmark.h:1694:35:
note: expanded from macro 'BENCHMARK_MAIN'
int main(int argc, char** argv) { \
^
***.cpp:21:1: error: conflicting types for 'main'
/opt/homebrew/Cellar/google-benchmark/1.8.4/include/benchmark/benchmark.h:1707:7:
note: expanded from macro 'BENCHMARK_MAIN'
int main(int, char**)
^
***.cpp:8:5: note: previous definition is here
int main() {
^
4 errors generated.
[ Babel evaluation exited with code 1 ]
***.cpp
の部分はランダム生成されたファイル名が入っている。
このファイルを見てみると以下のようになっている。
#include <benchmark/benchmark.h>
int main() {
#include <benchmark/benchmark.h>
int ackernamm(int m, int n) {
if (m == 0) {
return n + 1;
} else if (n == 0) {
return ackernamm(m - 1, 1);
} else {
return ackernamm(m - 1, ackernamm(m, n - 1));
}
}
static void BM_SomeFunction(benchmark::State& state) {
// Perform setup here
for (auto _ : state) {
ackernamm(3, 3);
}
}
// Register the function as a benchmark
BENCHMARK(BM_SomeFunction);
// Run the benchmark
BENCHMARK_MAIN();
return 0;
}
org-babelは実行時に main
関数でWrapされた状態のファイルを作成している。
BENCHMARK_MAIN();
は マクロで、実行時に main関数に置換されるため、2つのmain関数が混在してエラーになる。
そこで、最終的には :main no
タグを付けることでmain関数でのWrapを制限し、エラーが回避できる。
解決策#
#+HEADER: :includes <benchmark/benchmark.h>
#+begin_src C++ :main no :flags "-std=c++14 $(pkg-config --cflags --libs benchmark) -pthread"
#include <benchmark/benchmark.h>
int ackernamm(int m, int n) {
if (m == 0) {
return n + 1;
} else if (n == 0) {
return ackernamm(m - 1, 1);
} else {
return ackernamm(m - 1, ackernamm(m, n - 1));
}
}
static void BM_SomeFunction(benchmark::State& state) {
// Perform setup here
for (auto _ : state) {
ackernamm(3, 3);
}
}
// Register the function as a benchmark
BENCHMARK(BM_SomeFunction);
// Run the benchmark
BENCHMARK_MAIN();
#+end_src
main関数を書きたい場合#
展開後の、main関数の中身を直接書くことで、 BENCHMARK_MAIN();
マクロを使用せずに実行することができる。
#+HEADER: :includes <benchmark/benchmark.h>
#+begin_src C++ :results scalar :exports both :flags "-std=c++14 $(pkg-config --cflags --libs benchmark) -pthread"
#include <benchmark/benchmark.h>
int ackernamm(int m, int n) {
if (m == 0) {
return n + 1;
} else if (n == 0) {
return ackernamm(m - 1, 1);
} else {
return ackernamm(m - 1, ackernamm(m, n - 1));
}
}
static void BM_SomeFunction(benchmark::State& state) {
// Perform setup here
for (auto _ : state) {
ackernamm(3, 3);
}
}
// Register the function as a benchmark
BENCHMARK(BM_SomeFunction);
int main(int argc, char *argv[])
{
::benchmark::Initialize(&argc, argv);
if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1;
::benchmark::RunSpecifiedBenchmarks();
::benchmark::Shutdown();
}
#+end_src
さいごに#
google/benchmarkの記事ではないので、メソッドについては深く触れないが、 Rnages()
メソッドを用いることで、複数パラメータを振って評価することができる。
個人的には、 :main no
タグと BENCHMARK_MAIN();
を用いて短く記載できることに利点を感じる。
以下のコードをスニペットに登録しておいた。
#+HEADER: :includes <benchmark/benchmark.h>
#+BEGIN_SRC C++ :results scalar :exports both :main no :flags "-stdlib=libc++ -std=c++14 $(pkg-config --cflags --libs benchmark) -pthread"
int ackernamm(int m, int n) {
if (m == 0) {
return n + 1;
} else if (n == 0) {
return ackernamm(m - 1, 1);
} else {
return ackernamm(m - 1, ackernamm(m, n - 1));
}
}
static void BM_ackernamm(benchmark::State& state) {
for (auto _ : state)
ackernamm(state.range(0), state.range(1));
}
BENCHMARK(BM_ackernamm)->Ranges({{0, 3}, {0, 3}});
BENCHMARK_MAIN();
#+END_SRC
#+RESULTS:
#+begin_example
-----------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------
BM_ackernamm/0/0 7.88 ns 7.85 ns 89593119
BM_ackernamm/1/0 8.91 ns 8.88 ns 78907025
BM_ackernamm/3/0 34.7 ns 34.6 ns 20173725
BM_ackernamm/0/1 7.85 ns 7.83 ns 89050593
BM_ackernamm/1/1 12.9 ns 12.8 ns 54442085
BM_ackernamm/3/1 197 ns 197 ns 3517800
BM_ackernamm/0/3 8.00 ns 7.90 ns 88878731
BM_ackernamm/1/3 21.1 ns 21.0 ns 33271543
BM_ackernamm/3/3 7033 ns 7031 ns 100921
#+end_example
JupyterNotebookみたいな操作イメージ ↩︎