性能解析

ソフトウェア工学における性能解析（せいのうかいせき）または性能分析（せいのうぶんせき）（）とは、動的プログラム解析の一種であり、プログラムの実行を通して情報を収集することでプログラムの性能を解析することを言う。逆にプログラムを実行せずに行う解析を静的コード解析と呼ぶ。性能解析の目的は、実行時間やメモリ使用量を最適化するためにプログラムのどの部分を改良すべきかを決定することである（ボトルネック、アムダールの法則参照）。

 プロファイラの利用 
プロファイラ（）は性能解析ツールであり、プログラム実行時の各種情報を収集する。特に、関数呼び出しの頻度やそれにかかる時間を計測する。出力は記録したイベントの羅列（トレース）の場合と、観測したイベント群の統計的要約（プロファイル）の場合がある。プロファイラがデータを収集する技法は様々で、ハードウェアの割り込みを利用する技法、コードに命令を埋め込む技法、オペレーティングシステムのフックを利用する技法、性能カウンタを利用する技法などがある。プロファイラの使用はパフォーマンスエンジニアリングにおいて重要である。

プロファイルは統計的な総和としてソースコードの位置と関連付けてイベントを記録するため、データのサイズは対象となったコードのサイズにほぼ比例する。一方、トレースのサイズはプログラムの実行時間に比例したサイズとなってしまうため、実用的でないこともある。並列性のないプログラムでは、一般にプロファイルで十分だが、並列性のあるプログラムでの性能問題（メッセージ待ちや同期問題）については、イベントが時系列としてどういう順序で発生しているかが重要であるため、完全なトレースを必要とすることが多い。

 歴史 
UNIXにおけるプロファイラを使った性能解析は、1979年、"prof" ツールが導入されたころまで遡る。prof は関数毎にかかった時間をリストアップするツールである。1982年、"gprof" によって完全なコールグラフによる解析へと進化した（Gprof: a Call Graph Execution Profiler ）。

1994年、ディジタル・イクイップメント・コーポレーションの Amitabh Srivastava と Alan Eustace は ATOM に関する論文を発表した 。ATOM はプログラムをプロファイラに変換するシステムである。つまり、コンパイル時に解析用の命令を挿入して、実行時にその命令によって解析データが出力される。解析対象のプログラム自身に変更を加える技法を "instrumentation"（計測化）とも呼ぶ。

2004年、これまでで最も影響の大きかった論文（ACM SIGPLAN の学会誌 Programming Language Design and Implementation 誌上）20本に Gprof の論文と ATOM の論文が選ばれた 。

 出力による分類 
フラット・プロファイラ
ルーチン毎の平均実行時間を計算し、呼び出し側などのコンテキストを考慮した詳細な解析を行わない。
コールグラフ・プロファイラ
呼び出し回数や頻度を示し、呼び出し関係の連鎖に従った解析が可能。ただしコンテキスト（引数の値など）は考慮されない。

 データ収集法 

 イベントベースのプロファイラ 
以下のプログラミング言語はイベントベースのプロファイラを持つ。
.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 がつく。
JavaScript
例えば、Firebugや Google Chromeや Safariの 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秒）と推定される 。性能解析において問題となるのは時間のかかる呼び出し回数の多い関数であるため、このような誤差はあまり問題とはならない。

よく使われる統計的プロファイラとしては、GNUプロジェクトの gprof、Oprofile、シリコングラフィックスの Pixie などがある。

 手段 
マニュアル（手動）
実行時間を計算するコードをプログラマが明示的に組み込む。
コンパイラ補助
コンパイル時にプロファイラ用コードを組み込む。"gcc -pg ..." など。
バイナリ変換
コンパイル済みのバイナリに命令を追加する。ATOM など。
ランタイム補助
ツールの監視下でプログラムを実行する。PIN、Valgrind など。
ランタイム挿入
実行時にコードを修正し、ヘルパー関数へ飛ぶようにする。DynInst など。
ハイパーバイザ
ハイパーバイザ（VMモニタ）上で修正されていないプログラムを実行し、ハイパーバイザが情報を収集する。SIMMONなど。
シミュレータ
命令セットシミュレータ上で動作させて、情報を収集する。SIMMON など。

 実際の性能解析と性能強化 
逐次型プログラムに無限ループがある場合、問題を発見する最も単純な方法はデバッガを使って一時停止させ（無限ループしている箇所が不明なのでブレークポイントではない）、そのときのコールスタックを調査することである。コールスタック上には呼び出されている関数のアドレスが積まれている。どの関数が無限ループしているかは、シングルステップ実行させてコールスタックの変化を追っていけばわかる。

無限ループでなくとも同様の技法が活用できる。必要に応じて外側にループを追加することで時間のかかっている部分を数秒以上かかるように修正できる。そうしておいて、問題の箇所と思われるタイミングでデバッガによる一時停止を行い、コールスタックを確認する。これを繰り返していくことで何が問題なのか、どこを修正すればよいかが明確化される。

このような性能強化は、プログラムの誤動作を引き起こさないで単に遅くする種類のバグを修正する作業である。このようなバグをスラグ（slug = slowness bug）と呼ぶことがある。プログラムにはバグとスラグが含まれており、ソフトウェアテストによってバグは除去されるが、スラグは性能解析をしないと除去されない。

スラグにはいくつかの種類がある。意図的にプログラムの実行時間を長くするようなやり方は意図せずに行われることもある。最もよくあるスラグはループ内部で実行時間のほとんどを費やす「ホットスポット; hot spot」と言われる種類のものである。例えば、線型探索に時間がかかっている場合、それが二分探索で改善できるなら「ホットスポット」と言える。ホットスポットは必ずしもスラグとはいえないこともある。むしろ、ホットスポットとなっている関数をループ内で何度も呼び出す側の問題であることも多い。

また、別のスラグとして、ある問題に最適でない汎用的すぎるデータ構造を使っているために性能を低下させている場合がある。例えば、要素数が少ないなら、単純な配列による線型探索の方が複雑なハッシュテーブルなどよりも高速である。この種のスラグは、動的メモリ確保および解放に時間がかかっていることで判明することが多い。

別のスラグとして、データベースなどから有益な情報をまとめて収集する強力な関数を作成した場合があげられる。このような関数を何度も使うことで性能低下が発生する。このようなことが発生する要因として不適切なカプセル化が考えられる。

性能解析において、統計的プロファイラの精度はあまり重要ではない。典型的なスラグは実行時間の多くを消費している。従って、細部に拘っても性能は改善されない。また、実際の性能強化においては、スタックトレースなどの情報の方が重要である。

 主なプロファイラ 

 多言語対応 
 Performance Application Programming Interface (PAPI) はのマイクロプロセッサに備わっているハードウェア性能カウンタの移植性のあるAPI
 gprof GNU プロファイラ。GNU Binutils の一部。視覚化ツールとして VCG tools があり、Call Graph Drawing Interface (CGDI) や kprof と組み合わせて利用可能。主に C/C++ 向けだが、他の言語でも動作する。
 Valgrind x86-Linux に特化したデバッグおよびプロファイラシステム（GPL）。メモリ管理上のバグやスレッド関連のバグを自動検出できる。フロントエンドとして alleyoop がある。任意の言語およびアセンブラで利用可能。
 PerfSuite Linux 向けのオープンソースの性能解析ツール群。こちら にチュートリアル（英語）がある。
 OProfile Linux 向けのGPLベースのプロファイラ
 CodeAnalyst AMD のフリーな性能解析ツール（Windows版）Linux版 の CodeAnalyst もある。いずれも AMD の CPU を使用しているシステム向け。
 Sysprof Linux 用統計的プロファイラ。カーネルモジュールを使って動作中の全プロセスの情報を収集する。プロセス間の呼び出し関係を視覚的に表示できる。
 DynaProf は、実行時にアプリケーションのアドレス空間に直接性能解析用命令を埋め込むことで性能解析を行うツール。PAPIを通して任意の性能情報を収集できる。
 TAU PAPI を利用した性能解析ツール群。
 DynInst Homepage は、実行時にコードを書き換えるためのAPI（これを利用して命令を埋め込む）
 Oracle Solaris Studio Performance Analyzer - Oracle Solaris Studio に含まれている（フリー）
 Intel VTune Amplifier XE - C, C++, C#, Fortran, アセンブラ, Java
 Windows Performance Toolkit - Windows ADK (アセスメント＆デプロイメント キット) の一部
 shinyprofiler - C, C++, Lua
 Simpleperf - Androidアプリケーション用。Java/Kotlin、C/C++に対応。

 Java 
 Java VisualVM - JDK標準搭載
 Test and Performance Tools Platform Project (TPTP) Eclipse用プロファイラプラグイン
 NetBeans Debugger and Profiler - NetBeans用プロファイラ・アドイン
 JProfiler
 JRat Java用ランタイム解析ツールキット。LGPLライセンス
 YourKit Java Profiler

 .NET Framework 
 Visual Studio プロファイリング ツール - Visual Studio 2012 は Express 以外に搭載
 CLR Profiler フリーなCLRプロファイラ（マイクロソフト）
 NProf オープンソースの .NET 用統計的プロファイラ

 Delphi 
 ProDelphi Delphi 2〜7、2005、2006、Turbo Delphi (Win32) 向けのプロファイラ（フリー版もある）
 GpProfile Delphi 2, 3, 4, 5 向けのオープンソースプロファイラ

 Perl 
 Devel::DProf オリジナルの Perl コードプロファイラ
 Devel::Profiler より新しい Perl コードプロファイラ

 ActionScript 
 Flex Builder プロファイラ
 IntelliJ IDEA Flash/Flex Profiler plugin - IntelliJ IDEAプラグイン

 JavaScript 
 Firebug - Firefox
 Google Chrome Developer Tools
 Safari Developer Tools
 Nodetime - Node.js