org-mode 内でも google/benchmark を使いたい
- 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 というコード実行機能を使うことが多い34。
簡単にベンチマークが実行できるようになるので、 セットアップのメモとテンプレートを残す。
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) {10 collapsed lines
// Perform setup here for (auto _ : state) { ackernamm(0, 0); }}// Register the function as a benchmarkBENCHMARK(BM_SomeFunction);// Run the benchmarkBENCHMARK_MAIN();#+end_src1つ目のエラー
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 arm64collect2: 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 herestatic 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 hereBENCHMARK_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:8 collapsed lines
note: expanded from macro 'BENCHMARK_MAIN' int main(int, char**) ^***.cpp:8:5: note: previous definition is hereint 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)); }}
12 collapsed lines
static void BM_SomeFunction(benchmark::State& state) { // Perform setup here for (auto _ : state) { ackernamm(3, 3); }}// Register the function as a benchmarkBENCHMARK(BM_SomeFunction);// Run the benchmarkBENCHMARK_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) {10 collapsed lines
// Perform setup here for (auto _ : state) { ackernamm(3, 3); }}// Register the function as a benchmarkBENCHMARK(BM_SomeFunction);// Run the benchmarkBENCHMARK_MAIN();#+end_srcmain関数を書きたい場合
展開後の、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) {15 collapsed lines
// Perform setup here for (auto _ : state) { ackernamm(3, 3); }}// Register the function as a benchmarkBENCHMARK(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));21 collapsed lines
}
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 89593119BM_ackernamm/1/0 8.91 ns 8.88 ns 78907025BM_ackernamm/3/0 34.7 ns 34.6 ns 20173725BM_ackernamm/0/1 7.85 ns 7.83 ns 89050593BM_ackernamm/1/1 12.9 ns 12.8 ns 54442085BM_ackernamm/3/1 197 ns 197 ns 3517800BM_ackernamm/0/3 8.00 ns 7.90 ns 88878731BM_ackernamm/1/3 21.1 ns 21.0 ns 33271543BM_ackernamm/3/3 7033 ns 7031 ns 100921#+end_exampleFootnotes
-
JupyterNotebookみたいな操作イメージ ↩