性能解析
出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2023/03/28 02:32 UTC 版)
歴史
UNIXにおけるプロファイラを使った性能解析は、1979年、"prof" ツールが導入されたころまで遡る。prof は関数毎にかかった時間をリストアップするツールである。1982年、"gprof" によって完全なコールグラフによる解析へと進化した(Gprof: a Call Graph Execution Profiler [1])。
1994年、ディジタル・イクイップメント・コーポレーションの Amitabh Srivastava と Alan Eustace は ATOM に関する論文を発表した [2]。ATOM はプログラムをプロファイラに変換するシステムである。つまり、コンパイル時に解析用の命令を挿入して、実行時にその命令によって解析データが出力される。解析対象のプログラム自身に変更を加える技法を "instrumentation"(計測化)とも呼ぶ。
2004年、これまでで最も影響の大きかった論文(ACM SIGPLAN の学会誌 Programming Language Design and Implementation 誌上)20本に Gprof の論文と ATOM の論文が選ばれた [3]。
出力による分類
- フラット・プロファイラ
- ルーチン毎の平均実行時間を計算し、呼び出し側などのコンテキストを考慮した詳細な解析を行わない。
- コールグラフ・プロファイラ
- 呼び出し回数や頻度を示し、呼び出し関係の連鎖に従った解析が可能。ただしコンテキスト(引数の値など)は考慮されない。
データ収集法
イベントベースのプロファイラ
以下のプログラミング言語はイベントベースのプロファイラを持つ。
- .NET Framework
- プロファイラエージェントを COM サーバーとしてアタッチできる。Visual Studio で解析できる。
- Java
- JDK 1.4 までは、Java Virtual Machine Profiler Interface (JVMPI) によりプロファイラへのフックが提供され、メソッド呼び出し、クラスのロード/アンロード、スレッドの出入りなどのイベントを捉えられた。これは、Java 5からは、Java Virtual Machine Tool Interface (JVMTI) になった。Java 5以降は、Java Management Extension API もある。Java 6 update 7 以降には Java VisualVM がつく[1]。
- JavaScript
- 例えば、Firebug[2]や Google Chrome[3]や Safari[4]の Developer tools などで、関数の呼び出し回数、処理時間などが計測できる。
- Python
- コールグラフに基づくプロファイル情報を収集し、'sys.set_profile()' モジュールを使って c_{call,return,exception} や python_{call,return,exception} といったイベントを捉える。
- Ruby
- Ruby も Python と同様にインタフェースによってプロファイリングを行う。
統計的プロファイラ
プロファイラによってはサンプリングによって情報収集する。サンプリング型プロファイラは、オペレーティングシステムの割り込みを使って、対象プログラムのプログラムカウンタを一定間隔で調べる。サンプリング型のプロファイラは一般に精度が低いが、対象プログラムを通常とほぼ同じ速度で実行させることができる。
プロファイラによっては対象プログラムに情報を収集するための命令を追加するものもある。この場合、プログラムの性能が変化し、結果が不正確になったりするが、詳しい情報を収集することができる。
得られるデータは正確ではないが、統計的な近似になっている。一定間隔でサンプリングしてその時点で実行中だった関数がそのサンプリング間隔の間ずっと動作していたとして実行時間を収集していくのが一般的である。従って、サンプリング間隔が 0.01 秒であれば、実際にその関数が動作していた時間はほとんど 0 から 0.02 秒までありうる。その関数がサンプリングされる回数が多ければ多いほど誤差は小さくなっていく。例えば、サンプリング間隔 0.01 秒である関数の実行時間が 1 秒という結果が得られた場合、サンプリングされた回数 n は 100 回であり、誤差はその平方根、つまり 10回(= 0.1秒)と推定される [4] 。性能解析において問題となるのは時間のかかる呼び出し回数の多い関数であるため、このような誤差はあまり問題とはならない。
よく使われる統計的プロファイラとしては、GNUプロジェクトの gprof、Oprofile、シリコングラフィックスの Pixie などがある。
手段
- マニュアル(手動)
- 実行時間を計算するコードをプログラマが明示的に組み込む。
- コンパイラ補助
- コンパイル時にプロファイラ用コードを組み込む。"gcc -pg ..." など。
- バイナリ変換
- コンパイル済みのバイナリに命令を追加する。ATOM など。
- ランタイム補助
- ツールの監視下でプログラムを実行する。PIN、Valgrind など。
- ランタイム挿入
- 実行時にコードを修正し、ヘルパー関数へ飛ぶようにする。DynInst など。
- ハイパーバイザ
- ハイパーバイザ(VMモニタ)上で修正されていないプログラムを実行し、ハイパーバイザが情報を収集する。SIMMONなど。
- シミュレータ
- 命令セットシミュレータ上で動作させて、情報を収集する。SIMMON など。
実際の性能解析と性能強化
逐次型プログラムに無限ループがある場合、問題を発見する最も単純な方法はデバッガを使って一時停止させ(無限ループしている箇所が不明なのでブレークポイントではない)、そのときのコールスタックを調査することである。コールスタック上には呼び出されている関数のアドレスが積まれている。どの関数が無限ループしているかは、シングルステップ実行させてコールスタックの変化を追っていけばわかる。
無限ループでなくとも同様の技法が活用できる。必要に応じて外側にループを追加することで時間のかかっている部分を数秒以上かかるように修正できる。そうしておいて、問題の箇所と思われるタイミングでデバッガによる一時停止を行い、コールスタックを確認する。これを繰り返していくことで何が問題なのか、どこを修正すればよいかが明確化される。
このような性能強化は、プログラムの誤動作を引き起こさないで単に遅くする種類のバグを修正する作業である。このようなバグをスラグ(slug = slowness bug)と呼ぶことがある。プログラムにはバグとスラグが含まれており、ソフトウェアテストによってバグは除去されるが、スラグは性能解析をしないと除去されない。
スラグにはいくつかの種類がある。意図的にプログラムの実行時間を長くするようなやり方は意図せずに行われることもある。最もよくあるスラグはループ内部で実行時間のほとんどを費やす「ホットスポット; hot spot」と言われる種類のものである。例えば、線型探索に時間がかかっている場合、それが二分探索で改善できるなら「ホットスポット」と言える。ホットスポットは必ずしもスラグとはいえないこともある。むしろ、ホットスポットとなっている関数をループ内で何度も呼び出す側の問題であることも多い。
また、別のスラグとして、ある問題に最適でない汎用的すぎるデータ構造を使っているために性能を低下させている場合がある。例えば、要素数が少ないなら、単純な配列による線型探索の方が複雑なハッシュテーブルなどよりも高速である。この種のスラグは、動的メモリ確保および解放に時間がかかっていることで判明することが多い。
別のスラグとして、データベースなどから有益な情報をまとめて収集する強力な関数を作成した場合があげられる。このような関数を何度も使うことで性能低下が発生する。このようなことが発生する要因として不適切なカプセル化が考えられる。
性能解析において、統計的プロファイラの精度はあまり重要ではない。典型的なスラグは実行時間の多くを消費している。従って、細部に拘っても性能は改善されない。また、実際の性能強化においては、スタックトレースなどの情報の方が重要である。
性能解析と同じ種類の言葉
- 性能解析のページへのリンク