uchan note

プログラミングや電子工作の話題を書きます

std::function のオーバーヘッド計測

C++ の std::function による呼び出しオーバーヘッドを計測してみました.

実験環境

手持ちのノートパソコンで実験しました.

結果概要

std::function を経由したラムダ式の呼び出しは,直接呼び出した場合に比べ 5 倍程度のオーバーヘッドがある.

検証ソースコード

main.cpp

#include <iostream>
#include <functional>
#include <chrono>

using namespace std::chrono;

int main(int argc, char** argv) {
  const int kLoop = 300000000;
  int count = 0;
  auto f{
    [&]{ count += argc; }
  };
  std::function g{
    [&]{ count += argc; }
  };

  system_clock::time_point start, end;

  count = 0;
  start = system_clock::now();
  for (int i = 0; i < kLoop; ++i) {
    f();
  }
  end = system_clock::now();
  std::cout << count << ", "
            << duration_cast<milliseconds>(end - start).count() << "ms" << std::endl;

  count = 0;
  start = system_clock::now();
  for (int i = 0; i < kLoop; ++i) {
    g();
  }
  end = system_clock::now();
  std::cout << count << ", "
            << duration_cast<milliseconds>(end - start).count() << "ms" << std::endl;
}

検証手順

$ clang++ -std=c++17 -Wall -g -O0 -o stdfunc_opt0 main.cpp
$ clang++ -std=c++17 -Wall -g -O3 -o stdfunc_opt3 main.cpp
$ ./stdfunc_opt0
300000000, 856ms
300000000, 4508ms
$ ./stdfunc_opt0
300000000, 853ms
300000000, 4540ms
$ ./stdfunc_opt3
300000000, 0ms
300000000, 621ms
$ ./stdfunc_opt3
300000000, 0ms
300000000, 618ms

結果詳細

最適化なし(-O0)でコンパイルした場合,出力された機械語を見ると fg も call 命令にエンコードされています. したがって,最適化なしの場合の比率が std::function の呼び出しオーバーヘッドとなると思って良いと思います.

最適化あり(-O3)でコンパイルした場合, f の呼び出しは eax レジスタkLoop を乗ずるという処理に最適化されてしまっており全く参考になりません. 一方で g の呼び出しは std::function の処理自体はインライン展開されているものの,最後にラムダ式を呼ぶ部分は call が残っていました. したがって,g の呼び出しに 600 ミリ秒強というのはある程度参考になる値かと思います.

620 msec / 300000000 ≒ 2.07 nsec

ですので,std::function を使ったラムダ式の呼び出しは 1 回あたり 2.07 nsec ということになります. おそらくほとんどがキャッシュに載っている世界の話でしょうから,時間の絶対値に意味があるとは思いませんが.参考値として.