パフォーマンスツールは、プログラム実行中に特定のイベントに関するデータを記録し、プログラムパフォーマンスメトリックの測定基準にデータを変換します。
この章では、パフォーマンスツールによって収集したデータをどのように処理して表示するか、またどのようにパフォーマンス解析に使用するかについて説明します。パフォーマンスデータを収集するツールは複数あり、どのツールも「コレクタ」という用語で呼ばれています。同様に、パフォーマンスデータを解析するツールも複数あり、どのツールも「解析ツール」という用語で呼ばれています。
この章では、次の内容について説明します。
パフォーマンスデータの収集と格納については、第 3 章「パフォーマンスデータの収集」を参照してください。
パフォーマンスアナライザによるパフォーマンスデータの解析については、第 4 章「パフォーマンスアナライザツール」を参照してください。
Solaris OS が負荷を実行中にカーネルのプロファイリングを行う方法については、第 5 章「カーネルプロファイリング」を参照してください。
er_print ユーティリティーを使用したパフォーマンスデータの解析については、第 6 章「 er_print コマンド行パフォーマンス解析ツール」を参照してください。
コレクタは、プロファイルデータ、トレースデータ、および大域データの 3 種類のデータを収集します。
プロファイルデータの収集は、一定の間隔でプロファイルイベントを記録することによって行います。間隔は、システム時間を使用して取得した時間間隔、または特定のタイプのハードウェアイベントの数です。指定の間隔に達すると、シグナルがシステムに送られ、次の機会にデータが記録されます。
トレースデータの収集は、さまざまなシステム関数をラッパー関数で割り込み、それによってシステム関数の呼び出しをインターセプトし、呼び出しに関するデータを記録することによって行います。
大域データの収集は、さまざまなシステムルーチンを呼び出して情報を取得することによって行います。大域データパケットのことを標本と呼びます。
プロファイルデータとトレースデータの両方が特定のイベントに関する情報を含んでおり、いずれのデータの種類もパフォーマンスメトリックに変換されます。大域データはメトリックに変換されませんが、プログラムの実行を複数のタイムセグメントに分割するために使用できるマーカーを提供します。大域データは、タイムセグメントにおけるプログラム実行の概要を示します。
それぞれのプロファイルイベントやトレースイベントで収集されたデータパケットには、次の情報が含まれます。
データ識別用のヘッダー
高分解能のタイムスタンプ
スレッド ID
軽量プロセス (LWP) ID
プロセッサ (CPU) ID (オペレーティングシステムから提供できる場合)
呼び出しスタックのコピー。Java プログラムの場合、マシン呼び出しスタックと Java 呼び出しスタックの 2 つの呼び出しスタックが記録されます。
OpenMP プログラムの場合、現在の並行領域の識別子と、OpenMP の状態も収集されます。
スレッドと軽量プロセスについての詳細は、第 7 章「パフォーマンスアナライザとそのデータの内容」を参照してください。
こうした共通の情報のほかに、各イベントに固有のデータパケットには、データの種類に固有の情報が含まれます。コレクタが記録できるデータは、次の 5 種類です。
時間プロファイルデータ
ハードウェアカウンタのオーバーフロープロファイルデータ
同期待ちトレースデータ
ヒープトレース (メモリー割り当て) データ
MPI トレースデータ
これらの 5 種類のデータ、それらから派生したメトリック、およびメトリックの使用方法については、あとの項で説明します。6 種類目のデータ、大域標本データには呼び出しスタック情報が含まれないため、メトリックに変換できません。
時間ベースのプロファイル時に収集されるデータは、オペレーティングシステムが提供するメトリックによって異なります。
Solaris OS での時間ベースのプロファイルでは、各 LWP の状態が定期的な間隔で格納されます。この間隔はプロファイル間隔と呼ばれます。この情報は整数型の配列に格納され、カーネルの管理する 10 個のマイクロアカウンティング状態のそれぞれに、1 つの配列要素が使用されます。収集されたデータは、各状態で消費された、プロファイル間隔の分解能を持つ時間値に、パフォーマンスアナライザによって変換されます。デフォルトのプロファイル間隔は、約 10 ミリ秒 (10 ms) です。コレクタは、約 1 ミリ秒の高分解能プロファイル間隔と、約 100 ミリ秒の低分解能プロファイル間隔を提供し、OS で許されれば任意の間隔を許可します。引数を付けずに collect コマンドを実行すると、このコマンドが実行されるシステム上で許される範囲と分解能が出力されます。
時間ベースのデータから計算されるメトリックの定義を、次の表に示します。
表 2–1 Solaris タイミングメトリック
メトリック |
定義 |
---|---|
ユーザー CPU 時間 |
CPU のユーザーモードで実行中に使用される LWP 時間。 |
時計時間 |
LWP 1 で使用される LWP 時間。一般的な「時計時間」です。 |
LWP 合計時間 |
LWP 時間の総合計。 |
システム CPU 時間 |
CPU のカーネルモードまたはトラップ状態で実行中に使用される LWP 時間。 |
CPU 待ち時間 |
CPU の待機中に使用される LWP 時間。 |
ユーザーロック時間 |
ロックの待機中に使用される LWP 時間。 |
テキストページフォルト時間 |
テキストページの待機中に使用される LWP 時間。 |
データページフォルト時間 |
データページの待機中に使用される LWP 時間。 |
ほかの待ち時間 |
カーネルページ待機中に使用される LWP 時間、またはスリープ中か停止中に使用される時間。 |
マルチスレッドの実験では、全 LWP にまたがって時計時間以外の時間が集計されます。定義した時計時間は、MPMD (multiple-program multiple-data) プログラムには意味がありません。
タイミングメトリックは、プログラムがいくつかのカテゴリで時間を費やした部分を示し、プログラムのパフォーマンス向上に役立てることができます。
ユーザー CPU 時間が大きいということは、その場所で、プログラムが仕事の大半を行なっていることを示します。この情報は、アルゴリズムを再設計することによって特に有益となる可能性があるプログラム部分を見つけるのに使用できます。
システム CPU 時間が大きいということは、プログラムがシステムルーチンに対する呼び出しで多くの時間を使用していることを示します。
CPU 待ち時間が大きいということは、使用可能な CPU 以上に実行可能なスレッドが多いか、ほかのプロセスが CPU を使用していることを示します。
ユーザーロック時間が大きいということは、要求対象のロックをスレッドが取得できないことを示します。
テキストページフォルト時間が大きいということは、リンカーによって生成されたコードが、呼び出しまたは分岐で新しいページの読み込みが発生するようなメモリー上の配置になることを意味します。この種の問題は、マップファイルを作成し、使用することによって解決できます。パフォーマンスアナライザのオンラインヘルプの「マップファイルの作成と利用」を参照してください。
データページフォルト時間が大きいということは、データへのアクセスによって新しいページの読み込みが発生していること示します。この問題は、プログラムのデータ構造またはアルゴリズムを変更することによって解決できます。
Linux OS で利用できるメトリックは、ユーザー CPU 時間だけです。報告される合計 CPU 使用時間は正確ですが、アナライザは Solaris OS の場合ほど正確に実際のシステム CPU 時間の割合を判別できない場合があります。アナライザは軽量プロセス (LWP) のデータであるかのように情報を表示しますが、現実には Linux OS 上に LWP はなく、表示される LWP ID は実際にはスレッド ID です。
ハードウェアカウンタは、キャッシュミス、キャッシュストールサイクル、浮動小数点演算、分岐予測ミス、CPU サイクル、および実行対象命令といったイベントの追跡に使用されます。ハードウェアカウンタオーバーフローのプロファイルでは、LWP が動作している CPU の特定のハードウェアカウンタがオーバーフローしたときに、コレクタはプロファイルパケットを記録します。この場合、そのカウンタはリセットされ、カウントを続行します。プロファイルパケットには、オーバーフロー値とカウンタタイプが入っています。
各種の CPU ファミリが 2 〜 18 個の同時ハードウェアカウンタレジスタをサポートしています。コレクタは、複数のレジスタ上でデータを収集できます。各レジスタごとに、オーバーフローを監視するカウンタの種類を選択し、カウンタのオーバフロー値を設定することができます。ハードウェアカウンタには、任意のレジスタを利用できるものもあれば、特定のレジスタしか利用できないものもあります。このことは、1 つの実験であらゆるハードウェアカウンタの組み合わせを選択できるわけではないことを意味します。
パフォーマンスアナライザは、ハードウェアカウンタのオーバーフロープロファイルデータをカウントメトリックに変換します。循環型のカウンタの場合、報告されるメトリックは時間に変換されます。非循環型のカウンタの場合は、イベントの発生回数になります。複数の CPU を搭載したマシンの場合、メトリックの変換に使用されるクロック周波数が個々の CPU のクロック周波数の調和平均となります。プロセッサのタイプごとに専用のハードウェアカウンタセットがあり、またハードウェアカウンタの数が多いため、ハードウェアカウンタメトリックはここに記載していません。次の項で、どのような種類のハードウェアカウンタがあるかについて調べる方法を説明します。
ハードウェアカウンタの用途の 1 つは、CPU に出入りする情報フローに伴う問題を診断することです。たとえば、キャッシュミス回数が多いということは、プログラムを再構成してデータまたはテキストの局所性を改善するか、キャッシュの再利用を増やすことによってプログラムのパフォーマンスを改善できることを意味します。
一部のハードウェアカウンタは、同様の情報または関連情報を示します。たとえば、分岐予測ミスが発生すると、間違った命令が命令キャッシュに読み込まれ、これらの命令を正しい命令と置換しなければならなくなるため、分岐予測ミスと命令キャッシュミスが関連付けられることがよくあります。置換により、命令キャッシュミス、命令変換ルックアサイドバッファー (ITLB) ミス、またはページフォルトが発生する可能性があります。
多くの場合、ハードウェアカウンタオーバーフローは、イベントと対応するイベントカウンタにオーバーフローを発生させた命令のあとに 1 つまたは複数の命令をはさんで送られます。これは「スキッド」と呼ばれ、カウンタオーバーフロープロファイルの解釈を困難にします。原因となる命令を正確に識別するためのハードウェアサポートがないと、候補の原因となる命令の適切なバックトラッキング検索が行われる場合があります。
そのようなバックトラッキングが収集中にサポートされて指定されると、ハードウェアカウンタプロファイルパケットにはさらに、ハードウェアカウンタイベントに適した候補の、メモリー参照命令の PC (プログラムカウンタ) と EA (有効アドレス) が組み込まれます。解析中の以降の処理は、候補のイベント PC と EA を有効にするのに必要です。メモリー参照イベントに関するこのような追加情報があると、各種のデータ指向解析が容易になります。
候補のイベント PC と EA のバックトラッキングおよび記録も、時間プロファイルで指定できます。
ハードウェアカウンタはプロセッサ固有であるため、どのカウンタを利用できるかは、使用しているプロセッサによって異なります。パフォーマンスツールには、よく使われると考えられるいくつかのカウンタの別名が用意されています。コレクタから特定システム上で利用できるハードウェアカウンタの一覧を取り出すには、引数を付けないで collect をそのシステム上の端末ウィンドウに入力します。プロセッサとシステムがハードウェアカウンタプロファイルをサポートしている場合、collect コマンドは、ハードウェアカウンタに関する情報が入った 2 つのリストを出力します。最初のリストには「既知の」(別名を持つ) ハードウェアカウンタが入っており、2 番目のリストには、raw ハードウェアカウンタが入っています。
次に、カウンタリストに含まれるエントリの表示例を示します。既知と考えられるカウンタはリストの最初に表示され、そのあとに raw ハードウェアカウンタのリストが表示されます。この例の出力行はすべて、印刷用に書式が整えられています。
Well known HW counters available for profiling: cycles[/{0|1}],9999991 (’CPU Cycles’, alias for Cycle_cnt; CPU-cycles) insts[/{0|1}],9999991 (’Instructions Executed’, alias for Instr_cnt; events) dcrm[/1],100003 (’D$ Read Misses’, alias for DC_rd_miss; load events) ... Raw HW counters available for profiling: Cycle_cnt[/{0|1}],1000003 (CPU-cycles) Instr_cnt[/{0|1}],1000003 (events) DC_rd[/0],1000003 (load events) |
既知のハードウェアカウンタリストでは、最初のフィールド (たとえば、cycles) は、collect コマンドの -h counter... 引数に使用できる別名を示します。この別名は、 er_print コマンド内で使用する識別子でもあります。
リストの 2 番目のフィールドには、そのカウンタに使用可能なレジスタ、たとえば、[/{0|1}] が示されます。既知のカウンタの場合は、合理的なサンプルレートを提供するためにデフォルト値が選択されています。実際のレートは、かなり変化するため、デフォルト以外の値を指定する必要がある場合もあります。
3 番目のフィールドは、たとえば 9999991 など、カウンタのデフォルトのオーバーフロー値です。
4 番目のフィールドは、括弧で囲まれ、タイプ情報を含んでいます。これは、簡単な説明 (CPU Cycles など)、raw ハードウェアカウンタ名 (Cycle_cnt など)、およびカウントされる単位の種類 (CPU-cycles など) を提供し、最大 2 ワードまで含めることができます。
タイプ情報の最初のワードが、
load、store、または load-store のいずれかである場合、そのカウンタはメモリーに関連したものです。collect -h コマンド内でカウンタ名の前に + 符号を付ける (たとえば、+dcrm) ことにより、イベントの原因となった正確な命令と仮想アドレスを検索できます。+ 符号を使用すると、データ空間プロファイリングも使用可能になります。詳細については、 「「データオブジェクト」タブ」、「「データレイアウト」タブ」、および「メモリーオブジェクトのタブ」を参照してください。
not-program-related である場合、カウンタはほかのプログラムによって開始されたイベント、たとえば CPU 対 CPU のキャッシュスヌープなどを取り込みます。プロファイリングにカウンタを使用すると、警告が生成され、プロファイリングで呼び出しスタックが記録されません。
タイプ情報の 2 番目または唯一のワードが、
CPU-cycles である場合は、そのカウンタを使用して時間ベースのメトリックを提供できます。そのようなカウンタについて報告されるメトリックは、デフォルトでは包括的時間および排他的時間へ変換されますが、イベントカウントとして表示することもできます。
events である場合、メトリックは包括的および排他的イベントカウントであり、時間へ変換できません。
例に示した既知のハードウェアカウンタリストでは、タイプ情報に 1 ワードが含まれており、最初のカウンタの場合は CPU-cycles で、2 番目のカウンタの場合は、events となっています。3 番目のカウンタでは、タイプ情報に load events という 2 ワードが含まれています。
raw ハードウェアカウンタリストに含まれる情報は、既知のハードウェアカウンタリストに含まれる情報のサブセットです。それぞれの行には、cpu-track(1) によって使用された内部カウンタ名、そのカウンタを使用できるレジスタ番号 (単数または複数)、デフォルトのオーバーフロー値、およびカウンタ単位が含まれており、カウンタ単位は CPU-cycles か Events です。
カウンタがプログラムの実行に関連のないイベントを測定する場合、タイプ情報の最初のワードは not-program-related になります。そのようなカウンタの場合、プロファイリングで呼び出しスタックが記録されませんが、その代わりに、擬似関数 collector_not_program_related で使用された時間が示されます。スレッドと LWP ID は記録されますが、意味がありません。
raw カウンタのデフォルトのオーバーフロー値は 1000003 です。この値は、ほとんどの raw カウンタでは最適ではないため、raw カウンタを指定するときにタイムアウト値を指定する必要があります。
マルチスレッドプログラムでは、たとえば、1 つのスレッドによってデータがロックされていると、別のスレッドがそのアクセス待ちになることがあります。このため、複数のスレッドが実行するタスクの同期を取るために、プログラムの実行に遅延が生じることがあります。これらのイベントは同期遅延イベントと呼ばれ、Solaris または pthread のスレッド関数の呼び出しをトレースすることによって収集されます。同期遅延イベントを収集して、記録するプロセスを同期待ちのトレースといいます。また、ロック待ちに費やされる時間を待ち時間といいます。現在、同期待ちトレースは、Solaris OS を実行しているシステムにのみ利用できます。
ただし、イベントが記録されるのは、その待ち時間がしきい値 (ミリ秒単位) を超えた場合だけです。しきい値 0 は、待ち時間に関係なく、あらゆる同期遅延イベントをトレースすることを意味します。デフォルトでは、同期遅延なしにスレッドライブラリを呼び出す測定試験を実施して、しきい値を決定します。こうして決定された場合、しきい値は、それらの呼び出しの平均時間に任意の係数 (現在は 6) を乗算して得られた値です。この方法によって、待ち時間の原因が本当の遅延ではなく、呼び出しそのものにあるイベントが記録されないようになります。この結果として、同期イベント数がかなり過小評価される可能性がありますが、データ量は大幅に少なくなります。
Java プログラムの同期トレースは、スレッドが Java モニターを取得しようとしたときに生成されるイベントに基づいています。マシンと Java の呼び出しスタックの両方がこれらのイベントに関して収集されますが、JavaTM 仮想マシン (JVM) ソフトウェア内で使用される内部ロックに関しては、同期トレースデータは収集されません。マシン表現では、スレッド同期が _lwp_mutex_lock への呼び出しに委譲され、これらの呼び出しはトレースされないので同期データは表示されません。
表 2–2 同期待ちトレースメトリック
メトリック |
定義 |
---|---|
待ち時間が所定のしきい値を超えたときの同期ルーチン呼び出し回数。 |
|
所定のしきい値を超えた総待ち時間。 |
この情報から、関数またはロードオブジェクトが頻繁にブロックされるかどうか、または同期ルーチンを呼び出したときの待ち時間が異常に長くなっているかどうかを調べることができます。同期待ち時間が大きいということは、スレッド間の競合が発生していることを示します。競合は、アルゴリズムの変更、具体的には、ロックする必要があるデータだけがスレッドごとにロックされるように、ロックを構成し直すことで減らすことができます。
正しく管理されていないメモリー割り当て関数やメモリー割り当て解除関数を呼び出すと、データの使い方の効率が低下し、プログラムパフォーマンスが低下する可能性があります。ヒープトレースでは、C 標準ライブラリメモリー割り当て関数 malloc、realloc、valloc、memalign、および割り当て解除関数 free 上で割り込み処理を行うことによって、コレクタはメモリーの割り当てと割り当て解除の要求をトレースします。mmap への呼び出しはメモリー割り当てとして扱われ、これによって Java メモリー割り当てのヒープトレースイベントを記録することが可能になります。Fortran 関数 allocate および deallocate は C 標準ライブラリ関数を呼び出すので、これらのルーチンも間接的にトレースされます。
Java プログラムのヒーププロファイリングはサポートされません。
表 2–3 メモリー割り当て (ヒープトレース) メトリック
メトリック |
定義 |
---|---|
割り当て |
メモリー割り当て関数の呼び出し回数。 |
割り当てバイト数 |
メモリー割り当て関数の各呼び出しで割り当てられたバイト数の合計。 |
リーク |
対応する割り当て解除関数の呼び出しを持たなかったメモリー割り当て関数の呼び出し回数。 |
リークバイト数 |
割り当てられたが割り当て解除されなかったバイト数。 |
ヒープトレースデータを収集すれば、プログラム内のメモリーリークを見つけたり、十分なメモリーが割り当てられていない場所を確認したりできます。
メモリーリークのもう 1 つの定義は、dbx デバッグツールなどでよく使用されているもので、それによると、メモリーリークとは、プログラムのデータ空間内のどこにもポインタが存在しない、動的に割り当てられるメモリーブロックです。ここで使用されるリークの定義にはこの代替定義を含みますが、ポインタが存在するメモリーも含みます。
コレクタは、Message Passing Interface (MPI) ライブラリの呼び出しに関するデータを収集できます。現在、MPI トレースは、Solaris OS を実行しているシステムにのみ利用できます。データ収集の対象となる関数を次に示します。
MPI_Allgather |
MPI_Allgatherv |
MPI_Allreduce |
MPI_Alltoall |
MPI_Alltoallv |
MPI_Barrier |
MPI_Bcast |
MPI_Bsend |
MPI_Gather |
MPI_Gatherv |
MPI_Irecv |
MPI_Isend |
MPI_Recv |
MPI_Reduce |
MPI_Reduce_scatter |
MPI_Rsend |
MPI_Scan |
MPI_Scatter |
MPI_Scatterv |
MPI_Send |
MPI_Sendrecv |
MPI_Sendrecv_replace |
MPI_Ssend |
MPI_Wait |
MPI_Waitall |
MPI_Waitany |
MPI_Waitsome |
MPI_Win_fence |
MPI_Win_lock |
表 2–4 MPI トレースメトリック
メトリック |
定義 |
---|---|
MPI 受信 |
データを受信する MPI 関数の受信操作回数 |
MPI 受信バイト数 |
MPI 関数で受信したバイト数 |
MPI 送信 |
データを送信する MPI 関数の送信操作回数 |
MPI 送信バイト数 |
MPI 関数で送信したバイト数 |
MPI 時間 |
MPI 関数のすべての呼び出しに使用した時間 |
ほかの MPI 呼び出し |
ほかの MPI 関数の呼び出し数 |
受信または送信したとして記録されたバイト数は、呼び出しにおいて与えられるバッファーサイズです。この数値は、実際に受信または送信したバイト数よりも大きいことがあります。大域通信関数と集合通信関数においては、直接的なプロセッサ間通信が行われるとともにデータ転送やデータ再送の最適化が行われないという前提に基づき、送信または受信されるバイト数が最大値となります。
トレースされる MPI ライブラリの関数を、MPI 送信関数、MPI 受信関数、MPI 送受信関数、およびその他の MPI 関数に分類して表 2–5 にまとめます。
表 2–5 送信、受信、送受信、およびその他の MPI 関数の分類
カテゴリ |
関数 |
---|---|
MPI 送信関数 |
MPI_Bsend、MPI_Isend、MPI_Rsend、MPI_Send、MPI_Ssend |
MPI 受信関数 |
MPI_Irecv、MPI_Recv |
MPI 送受信関数 |
MPI_Allgather、MPI_Allgatherv、MPI_Allreduce、MPI_Alltoall、MPI_Alltoallv、MPI_Bcast、MPI_Gather、 MPI_Gatherv、MPI_Reduce、MPI_Reduce_scatter、MPI_Scan、MPI_Scatter、 MPI_Scatterv、MPI_Sendrecv、MPI_Sendrecv_replace |
その他の MPI 関数 |
MPI_Barrier、MPI_Wait、MPI_Waitall、MPI_Waitany、MPI_Waitsome、MPI_Win_fence、MPI_Win_lock |
MPI トレースデータを収集すると、MPI 呼び出しによって MPI プログラムのパフォーマンスに問題が生じている可能性がある場所を確認できます。発生する可能性のあるパフォーマンスの問題の例には、負荷分散、同期遅延、通信ボトルネックなどがあります。
大域データは、標本パケットと呼ばれるパケット単位でコレクタが記録します。各パケットには、ヘッダー、タイムスタンプ、ページフォルトや I/O データなどのカーネルからの実行統計、コンテキストスイッチ、および各種のページの常駐性 (ワーキングセットとページング) 統計が入っています。標本パケットに記録されるデータは、プログラムにとって大域的であり、パフォーマンスメトリックには変換されません。標本パケットを記録するプロセスのことを、標本収集と呼びます。
IDE の「デバッグ」ウィンドウや dbx で、ブレークポイントに達するなど、何らかの理由でプログラムが停止したとき (これを行うオプションが設定されている場合)。
標本収集の間隔の終了時 (定期的な標本収集を選択している場合)。標本収集の間隔は整数値 (秒単位) で指定します。デフォルト値は 1 秒です。
「デバッグ」->「パフォーマンスツールキット」->「コレクタを有効に」を選択し、「コレクタ」 ウィンドウで「定期的な標本」チェックボックスをオンにしたとき
dbx の collector sample record コマンドを使用し、標本を手動で記録したとき
このルーチンに対する呼び出しがコードに含まれている場合に collector_sample を呼び出したとき (「データ収集のプログラム制御」を参照)
collect コマンドで -l オプションが使用されている場合に指定した信号が送信されたとき (collect(1) のマニュアルページを参照)
収集が開始および終了したとき
dbx の collector pause コマンドで収集を一時停止したとき (一時停止の直前)、および dbx の collector resume コマンドで収集を再開したとき (再開の直後)
派生プロセスが作成される前後
パフォーマンスツールは、標本パケットに記録されたデータを使用して、時間期間別に分類します。この分類されたデータを標本と呼びます。特定の標本セットを選択することによってイベントに固有のデータをフィルタできるので、特定の期間に関する情報だけを表示させることができます。各標本の大域データを表示することもできます。
パフォーマンスツールは、標本ポイントのさまざまな種類を区別しません。標本ポイントを解析に利用するには、1 種類のポイントだけを記録対象として選択してください。特に、プログラム構造や実行シーケンスに関する標本ポイントを記録する場合は、定期的な標本収集を無効にし、dbx がプロセスを停止したとき、collect コマンドによってデータ記録中のプロセスにシグナルが送られたとき、あるいはコレクタ API 関数が呼び出されたときのいずれかの状況で記録された標本を使用します。
メトリックは、イベント固有のデータとともに記録される呼び出しスタックを使用して、プログラムの命令に対応付けられます。情報を利用できる場合には、あらゆる命令がそれぞれ 1 つのソースコード行にマップされ、その命令に割り当てられたメトリックも同じソースコード行に対応付けられます。この仕組みについての詳細は、第 7 章「パフォーマンスアナライザとそのデータの内容」を参照してください。
メトリックは、ソースコードと命令のほかに、より上位のオブジェクト (関数とロードオブジェクト) にも対応付けられます。呼び出しスタックには、プロファイルが取られたときに記録された命令アドレスに達するまでに行われた、一連の関数呼び出しに関する情報が含まれます。パフォーマンスアナライザは、この呼び出しスタックを使用し、プログラム内の各関数のメトリックを計算します。こうして得られたメトリックを関数レベルのメトリックといいます。
パフォーマンスアナライザが計算する関数レベルのメトリックには、排他的メトリック、包括的メトリック、および属性メトリックの 3 種類があります。
関数の排他的メトリックは、関数自体で発生するイベントから計算されます。これには、ほかの関数の呼び出しから発生したメトリックは含まれません。
包括的メトリックは、関数自体およびその関数が呼び出す関数で発生するイベントから計算されます。これには、ほかの関数の呼び出しから発生したメトリックが含まれます。
属性メトリックは、どれだけの包括的メトリックがほかの関数からの呼び出しまたはほかの関数の呼び出しが原因で発生したかを示します。つまり、属性メトリックは、ほかの関数に原因があるメトリックです。
呼び出しスタックの一番下にのみ現れる関数 (リーフ関数) では、その関数の排他的および包括的メトリックは同じになります。
排他的および包括的メトリックは、ロードオブジェクトについても計算されます。ロードオブジェクトの排他的メトリックは、そのロードオブジェクト内の全関数の関数レベルのメトリックを集計することによって計算されるメトリックです。ロードオブジェクトの包括的メトリックは、関数に対するのと同じ方法で計算されるメトリックです。
関数の排他的および包括的メトリックは、その関数を通るすべての記録経路に関する情報を提供します。属性メトリックは、関数を通る特定の経路に関する情報を提供します。その情報は、どれだけのメトリックが特定の関数呼び出しが原因で発生したかを示します。呼び出しに関わる 2 つの関数を、呼び出し元および呼び出し先と呼びます。呼び出しツリーにおいて、それぞれの関数の属性メトリックは次の意味を持ちます。
関数の呼び出し元の属性メトリックは、その関数の包括的メトリックのうち、各呼び出し元からの呼び出しが原因になっているメトリックを示します。呼び出し元の属性メトリックも合計したものが、関数の包括的メトリックです。
関数の呼び出し先の属性メトリックは、その関数の包括的メトリックのうち、各呼び出し先への呼び出しが原因になっているメトリックを示します。この場合、属性メトリックの合計と関数の排他的メトリックは、その関数の包括的メトリックに等しくなります。
メトリック間の関係は、次の等式で表すことができます。
呼び出し元または呼び出し先の属性メトリックと包括的メトリックを比較すると、さらに情報が得られます。
呼び出し元の属性メトリックとその包括的メトリックの差は、ほかの関数への呼び出しおよびその呼び出し元自体の動作が原因で発生したメトリックを示します。
呼び出し先の属性メトリックとその包括的メトリックの差は、その呼び出し先の包括的メトリックのうち、ほかの関数からのその呼び出し先への呼び出しが原因で発生したメトリックを示します。
プログラムのパフォーマンス改善が可能な場所を見つける方法には、次のものがあります。
図 2–1 は、排他的、包括的、属性メトリックを完全な呼び出しツリーで表しています。ここでは、中央の関数の関数 C に注目します。
プログラムの擬似コードは、図のあとに示されています。
Main 関数は、関数 A および関数 B を呼び出し、 Main 関数の包括的メトリックのうちの関数 A の 10 単位と関数 B の 20 単位の原因となっています。これらは、Main 関数の呼び出し先の属性メトリックです。その合計 (10+20) に Main 関数の排他的メトリックを加算すると、Main 関数の包括的メトリック (32) に等しくなります。
関数 A は関数 C の呼び出しにすべての時間を使用するため、排他的メトリックは 0 単位です。
関数 C は、関数 A および 関数 B の 2 つの関数によって呼び出され、関数 C の包括的メトリックのうちの関数 A の 10 単位と関数 B の 15 単位の原因になっています。これらは呼び出し元の属性メトリックです。その合計 (10+15) は、関数 C の包括的メトリック (25) に等しくなります。
呼び出し元の属性メトリックは、関数 A と B の包括的メトリックと排他的メトリックの差と等しくなります。これは、関数 A と B は関数 C のみを呼び出すことを意味します。実際、これらの関数がほかの関数を呼び出している場合がありますが、時間が短すぎて、実験に現れることはありません。
関数 C は、関数 E および関数 F の 2 つの関数を呼び出し、関数 C の包括的メトリックのうちの関数 E の 10 単位と関数 F の 10 単位の原因となっています。これらは、呼び出し先の属性メトリックです。その合計 (10+10) に関数 C の排他的メトリック (5) を加算すると、関数 C の包括的メトリック (25) に等しくなります。
関数 E と関数 F では、呼び出し先の属性メトリックと呼び出し元の包括的メトリックは等しくなります。これは、関数 E と関数 F が関数 C によってのみ呼び出されることを意味します。関数 E では、排他的メトリックと包括的メトリックが同じですが、関数 F では異なります。これは、関数 F はほかの関数 (関数 G) を呼び出しますが、関数 E はそうしないためです。
このプログラムの擬似コードを次に示します。
main() { A(); /2 単位の処理を行います。/ B(); } A() { C(10); } B() { C(7.5); /5 単位の処理を行います。/ C(7.5); } C(arg) { /C 自体で 20 %、E の呼び出しに 40 %、F の呼び出しに 40 % を実行し、合計「arg」単位の処理を行います。/ }
直接または間接のどちらの場合も、再帰関数呼び出しがあると、メトリックの計算が複雑になります。パフォーマンスアナライザは、関数の呼び出しごとではなく、その関数全体のメトリックを表示します。このため、一連の再帰呼び出しのメトリックを 1 つのメトリックに要約する必要があります。この要約によって、呼び出しスタックの最後の関数 (リーフ関数) から計算される排他的メトリックが影響を受けることはありませんが、包括的および属性メトリックは影響を受けます。
包括的メトリックは、イベントのメトリックと呼び出しスタック内の関数の包括的メトリックを合計することによって計算されます。再帰呼び出しスタックにおいてメトリックが複数回カウントされないようにするには、イベントのメトリックが、同じ関数の包括的メトリックに複数回加算されないようにします。
属性メトリックは、包括的メトリックから計算されます。もっとも簡単な再帰では、再帰関数は、それ自身ともう 1 つの関数 (呼び出しを開始する関数) の 2 つの呼び出し元を持ちます。最後の呼び出しですべての動作を終えた場合、再帰関数の包括的メトリックの原因になるのは、その再帰関数であり、呼び出しを開始した関数ではありません。これは、再帰関数の上位にあるすべての呼び出しの包括的メトリックは、メトリックの複数回のカウントを回避するために、ゼロと見なされるためです。ただし、呼び出しを開始した関数は、再帰呼び出しであるために、呼び出し先としての再帰関数の包括的メトリックの一部の原因になります。