gprofでプログラムを解析する

Last update: 2025/1/5


実行環境

解析用ファイルを出力する

今回はgprofでプログラムを解析するときの手順をメモしておく。解析するプログラムの題材として以前つくったモンテカルロ法で円周率を求めるC++のコードを使う。

まずは、-pgオプションをつけてソースコードをコンパイルする。※-pgオプションをつけることでパフォーマンスが低下することがあるので、オプションをつけるのは解析するときだけにする

$ g++ -pg pi.cpp

この状態でコンパイルしたプログラムを実行する。

$ ./a.out 10000000

すると、以下のようにgmon.outという解析用のバイナリファイルが出力される。

$ ls
a.out  gmon.out  pi.cpp

gprofで結果を確認する

さきほど出力されたgmon.outを使い、以下のようにコマンドを打つ。デフォルトでは結果がコンソール画面に表示されるので、以下のようにtxtファイルに出力したりlessなどとパイプすると確認しやすい。

$ gprof ./a.out gmon.out > gmon.txt

実行結果として大きく分けて2種類の結果が表示される。まず1つめは以下のFlat profile。

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 53.64      0.29     0.29    64103     0.00     0.00  std::mersenne_twister_engine::_M_gen_rand()
 19.09      0.40     0.10 20000000     0.00     0.00  double std::generate_canonical >(std::mersenne_twister_engine&)
  9.09      0.45     0.05 40000000     0.00     0.00  std::mersenne_twister_engine::operator()()
  3.64      0.47     0.02 20000000     0.00     0.00  std::uniform_real_distribution::param_type::b() const
  3.64      0.49     0.02 20000000     0.00     0.00  double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&)
  3.64      0.51     0.02 20000000     0.00     0.00  std::__detail::_Adaptor, double>::_Adaptor(std::mersenne_twister_engine&)
  2.73      0.53     0.01 20000000     0.00     0.00  double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&, std::uniform_real_distribution::param_type const&)
  1.82      0.54     0.01 40000000     0.00     0.00  std::uniform_real_distribution::param_type::a() const
  1.82      0.55     0.01                             _init
  0.91      0.55     0.01        2     2.50     2.50  bool __gnu_cxx::__is_null_pointer(char const*)
  …以下略…

各項目の意味も出力されるので説明は省くが、この結果からプログラムの実行の時間を占めている関数がわかり、プログラムのボトルネックを調べることができる。今回のプログラムだと擬似乱数生成エンジンの一部であるstd::mersenne_twister_engine<...>::_M_gen_rand()が実行時間の半分以上を占めていることがわかる。

また、以下のようなコールグラフも出力される。

granularity: each sample hit covers 4 byte(s) for 1.82% of 0.55 seconds

index % time    self  children    called     name
                                                 
[1]     98.2    0.00    0.54                 main [1]
                0.02    0.52 20000000/20000000     double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&) [2]
                0.00    0.00       1/2           std::__cxx11::basic_string, std::allocator >::basic_string >(char const*, std::allocator const&) [14]
                0.00    0.00       1/1           std::random_device::random_device() [15]
                0.00    0.00       1/1           std::__cxx11::stoi(std::__cxx11::basic_string, std::allocator > const&, unsigned long*, int) [39]
                0.00    0.00       1/1           std::random_device::operator()() [34]
                0.00    0.00       1/1           std::mersenne_twister_engine::mersenne_twister_engine(unsigned long) [36]
                0.00    0.00       1/1           std::uniform_real_distribution::uniform_real_distribution(double, double) [38]
                0.00    0.00       1/1           std::random_device::~random_device() [33]
-----------------------------------------------
                0.02    0.52 20000000/20000000     main [1]
[2]     97.3    0.02    0.52 20000000         double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&) [2]
                0.01    0.50 20000000/20000000     double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&, std::uniform_real_distribution::param_type const&) [3]
-----------------------------------------------
                0.01    0.50 20000000/20000000     double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&) [2]
[3]     93.6    0.01    0.50 20000000         double std::uniform_real_distribution::operator() >(std::mersenne_twister_engine&, std::uniform_real_distribution::param_type const&) [3]
                0.00    0.45 20000000/20000000     std::__detail::_Adaptor, double>::operator()() [4]
                0.02    0.00 20000000/20000000     std::__detail::_Adaptor, double>::_Adaptor(std::mersenne_twister_engine&) [9]
                0.02    0.00 20000000/20000000     std::uniform_real_distribution::param_type::b() const [8]
                0.01    0.00 40000000/40000000     std::uniform_real_distribution::param_type::a() const [10]
-----------------------------------------------
…以下略…

Call Graphを可視化するツールもあるようなので、また別の機会に試してみたいと思う。

参考文献

gprofの使い方メモ #gprof - Qiita

セミナー資料 公開ページ | HPCI:チューニング技法入門: Part 1 チューニングの基礎

タダでソフト開発の生産性と品質を上げる方法(10):プログラムの実行速度を瞬時に測定する「gprof」:山浦恒央の“くみこみ”な話(100)


Copyright (c) 2024 kd