man page of ut0s

org-mode 内でも google/benchmark を使いたい

2024-07-14
5-configs Emacsorg-modeC++org-babel
8 Minutes
1431 Words
  • 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謹製だし、使ってみるかと少し確認するコードを書いてみると、これが使いやすい。

自分は小さいコードの単位で試したい場合や試行メモを残して置きたい場合は、 Emacsorg-modeorg-babel というコード実行機能を使うことが多い34

簡単にベンチマークが実行できるようになるので、 セットアップのメモとテンプレートを残す。

org-babel で google/benchmark をリンクする

インストール

Terminal window
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 benchmark
BENCHMARK(BM_SomeFunction);
// Run the benchmark
BENCHMARK_MAIN();
#+end_src

1つ目のエラー

Macの場合は以下のようなエラーが出る。 Clangを使用するように強制してあげることで解決した。

Terminal window
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つ目のエラー

Terminal window
***.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:
8 collapsed lines
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));
}
}
12 collapsed lines
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) {
10 collapsed lines
// 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) {
15 collapsed lines
// 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));
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 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

Footnotes

  1. high_resolution_clock - cpprefjp C++日本語リファレンス

  2. 最初のコミット は2013年12月なので、10年以上開発されている

  3. JupyterNotebookみたいな操作イメージ

  4. Working with Source Code (The Org Manual)

  5. アッカーマン関数 - Wikipedia

Powerd by coffee
Copyright 2026
About
Privacy & Disclaimer