性能解析

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

1. プロファイラの利用
------------

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

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

2. 歴史
-----

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

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

2004年、これまでで最も影響の大きかった論文20本に Gprof の論文と ATOM の論文が選ばれた [2]。

3. 出力による分類
----------

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

4. データ収集法
---------

4.1. イベントベースのプロファイラ
-------------------

以下のプログラミング言語はイベントベースのプロファイラを持つ。

.NET Framework プロファイラエージェントを COM サーバーとしてアタッチできる。Visual Studio で解析できる。JavaJDK 1.4 までは、Java Virtual Machine Profiler Interface  によりプロファイラへのフックが提供され、メソッド呼び出し、クラスのロード/アンロード、スレッドの出入りなどのイベントを捉えられた。これは、Java 5からは、Java Virtual Machine Tool Interface  になった。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} といったイベントを捉える。RubyRuby も Python と同様にインタフェースによってプロファイリングを行う。

4.2. 統計的プロファイラ
--------------

プロファイラによってはサンプリングによって情報収集する。サンプリング型プロファイラは、オペレーティングシステムの割り込みを使って、対象プログラムのプログラムカウンタを一定間隔で調べる。サンプリング型のプロファイラは一般に精度が低いが、対象プログラムを通常とほぼ同じ速度で実行させることができる。

プロファイラによっては対象プログラムに情報を収集するための命令を追加するものもある。この場合、プログラムの性能が変化し、結果が不正確になったりするが、詳しい情報を収集することができる。

得られるデータは正確ではないが、統計的な近似になっている。一定間隔でサンプリングしてその時点で実行中だった関数がそのサンプリング間隔の間ずっと動作していたとして実行時間を収集していくのが一般的である。従って、サンプリング間隔が 0.01 秒であれば、実際にその関数が動作していた時間はほとんど 0 から 0.02 秒までありうる。その関数がサンプリングされる回数が多ければ多いほど誤差は小さくなっていく。例えば、サンプリング間隔 0.01 秒である関数の実行時間が 1 秒という結果が得られた場合、サンプリングされた回数 //n// は 100 回であり、誤差はその平方根、つまり 10回と推定される [3] 。性能解析において問題となるのは時間のかかる呼び出し回数の多い関数であるため、このような誤差はあまり問題とはならない。

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

4.3. 手段
-------

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

5. 実際の性能解析と性能強化
---------------

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

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

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

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

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

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

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

6. 主なプロファイラ
-----------

6.1. 多言語対応
----------

[4] はdate=2019-10のマイクロプロセッサに備わっているハードウェア性能カウンタの移植性のあるAPI
[5] GNU プロファイラ。GNU Binutils の一部。視覚化ツールとして [6] があり、[7]  や [8] と組み合わせて利用可能。主に C/C++ 向けだが、他の言語でも動作する。
Valgrind x86-Linux に特化したデバッグおよびプロファイラシステム。メモリ管理上のバグやスレッド関連のバグを自動検出できる。フロントエンドとして [9] がある。任意の言語およびアセンブラで利用可能。
[10] Linux 向けのオープンソースの性能解析ツール群。[11] にチュートリアルがある。
[12] Linux 向けのGPLベースのプロファイラ
[13]date=2019-10}} AMD のフリーな性能解析ツール[14]date=2019-10}} の CodeAnalyst もある。いずれも AMD の CPU を使用しているシステム向け。
[15] Linux 用統計的プロファイラ。カーネルモジュールを使って動作中の全プロセスの情報を収集する。プロセス間の呼び出し関係を視覚的に表示できる。
[16] は、実行時にアプリケーションのアドレス空間に直接性能解析用命令を埋め込むことで性能解析を行うツール。PAPIを通して任意の性能情報を収集できる。
[17] PAPI を利用した性能解析ツール群。
[18] は、実行時にコードを書き換えるためのAPI
Oracle Solaris Studio Performance Analyzer - Oracle Solaris Studio に含まれている
[19] - C, C++, C#, Fortran, アセンブラ, Java
[20] - Windows ADK  の一部
[21] - C, C++, Lua
[22] - Androidアプリケーション用。Java/Kotlin、C/C++に対応。

6.2. Java
---------

[23] - JDK標準搭載
[24] Eclipse用プロファイラプラグイン
[25] - NetBeans用プロファイラ・アドイン
[26]
[27] Java用ランタイム解析ツールキット。LGPLライセンス
[28]

6.3. .NET Framework
-------------------

[29] - Visual Studio 2012 は Express 以外に搭載
[30] フリーなCLRプロファイラ
[31] オープンソースの .NET 用統計的プロファイラ

6.4. Delphi
-----------

[32] Delphi 2〜7、2005、2006、Turbo Delphi  向けのプロファイラ
[33] Delphi 2, 3, 4, 5 向けのオープンソースプロファイラ

6.5. Perl
---------

[34] オリジナルの Perl コードプロファイラ
[35] より新しい Perl コードプロファイラ

6.6. ActionScript
-----------------

[36]
[37] - IntelliJ IDEAプラグイン

6.7. JavaScript
---------------

Firebug - Firefox
Google Chrome Developer Tools
Safari Developer Tools
[38] - Node.js

7. 関連項目
-------

パフォーマンスエンジニアリング
性能予測
最悪実行時間

8. 参照
-----

9. 外部リンク
--------

サンプルデモ "[39]" by Ian Huff。マイクロソフトの開発者で Visual Studio Team System 2005 におけるプロファイラのデモを行っている。