パフォーマンスツールは、プログラムの実行中に特定のイベントに関するデータを記録し、メトリックスと呼ばれるプログラムパフォーマンスの測定基準にデータを変換します。メトリックスは、関数、ソース行、および命令に対して表示されます。
この章では、パフォーマンスツールによって収集したデータをどのように処理して表示するか、またどのようにパフォーマンス解析に使用するかについて説明します。パフォーマンスデータを収集するツールは複数あり、どのツールも「コレクタ」という用語で呼ばれています。同様に、パフォーマンスデータを解析するツールも複数あり、どのツールも「解析ツール」という用語で呼ばれています。
この章では、次の内容について説明します。
パフォーマンスデータの収集と格納については、第 3 章パフォーマンスデータの収集を参照してください。
コレクタは、プロファイルデータ、トレースデータ、および大域データの 3 種類のデータを収集します。
プロファイルデータの収集は、一定の間隔でプロファイルイベントを記録することによって行います。間隔は、システム時間を使用して取得した時間間隔、または特定のタイプのハードウェアイベントの数です。指定の間隔に達すると、シグナルがシステムに送られ、次の機会にデータが記録されます。
トレースデータの収集は、さまざまなシステム関数をラッパー関数で割り込み、それによってシステム関数の呼び出しをインターセプトし、呼び出しに関するデータを記録することによって行います。
大域データの収集は、さまざまなシステムルーチンを呼び出して情報を取得することによって行います。大域データパケットのことを標本と呼びます。
プロファイルデータとトレースデータの両方が特定のイベントに関する情報を含んでおり、いずれのデータの種類もパフォーマンスメトリックスに変換されます。大域データはメトリックスに変換されませんが、プログラムの実行を複数のタイムセグメントに分割するために使用できるマーカーを提供します。大域データは、タイムセグメントにおけるプログラム実行の概要を示します。
それぞれのプロファイルイベントやトレースイベントで収集されたデータパケットには、次の情報が含まれます。
データ識別用のヘッダー
高分解能のタイムスタンプ
スレッド ID
軽量プロセス (LWP) ID
プロセッサ (CPU) ID (オペレーティングシステムから提供できる場合)
呼び出しスタックのコピー。Java プログラムの場合、マシン呼び出しスタックと Java 呼び出しスタックの 2 つの呼び出しスタックが記録されます。
OpenMP プログラムの場合、現在の並行領域の識別子と、OpenMP の状態も収集されます。
スレッドと軽量プロセスについての詳細は、第 6 章パフォーマンスアナライザとそのデータについてを参照してください。
こうした共通の情報のほかに、各イベントに固有のデータパケットには、データの種類に固有の情報が含まれます。コレクタが記録できるデータは、次の 5 種類です。
時間プロファイルデータ
ハードウェアカウンタのオーバーフロープロファイルデータ
同期待ちトレースデータ
ヒープトレース (メモリー割り当て) データ
MPI トレースデータ
これらの 5 種類のデータ、それらから派生したメトリックス、およびメトリックスの使用方法については、あとの項で説明します。6 種類目のデータ、大域標本データには呼び出しスタック情報が含まれないため、メトリックスに変換できません。
時間ベースのプロファイル時に収集されるデータは、オペレーティングシステムが提供するメトリックスによって異なります。
Solaris OS での時間ベースのプロファイルでは、各 LWP の状態が定期的な間隔で格納されます。この時間間隔は、プロファイル間隔と呼ばれます。この情報は整数の配列に格納されます。その配列の 1 つの要素は、カーネルによって維持される 10 個のマイクロアカウンティングの各状態に使用されます。収集されたデータは、各状態で消費された、プロファイル間隔の分解能を持つ時間値に、パフォーマンスアナライザによって変換されます。デフォルトのプロファイル間隔は、約 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 時間の割合を判別できない場合があります。アナライザは軽量プロセス (LightWeight Process、LWP) のデータであるかのように情報を表示しますが、実は Linux OS 上に LWP はなく、表示される LWP ID は実際にはスレッド ID です。
時間プロファイリングデータは、Oracle Message Passing Toolkit (以前の Sun HPC ClusterTools) で実行される MPI 実験で収集できます。Oracle Message Passing Toolkit はバージョン 8.1 またはそれ以降である必要があります。
Linux で Oracle Message Passing Toolkit 8.2 または 8.2.1 を使用する場合、回避策が必要になる場合があります。バージョン 8.1 または 8.2.1c の場合、または Oracle Solaris Studio コンパイラを使用している場合はどのバージョンでも回避策は必要ありません。回避策については、docs.sun.com の Oracle Solaris Studio 12.2 Collection - Japanese 内の『Oracle Solaris Studio 12.2 リリースの新機能』を参照してください。
MPI 実験で時間プロファイリングデータを収集すると、次の 2 つのメトリックスが追加されます。
MPI 作業: 要求やメッセージの処理など、プロセスが MPI ランタイム実行作業の内部にある場合に蓄積されます。
MPI 待機: プロセスが MPI ランタイムの内部にあり、イベント、バッファー、またはメッセージを待機している場合に蓄積されます。
Solaris OS では、MPI 作業は作業が直列または並列に実行される場合に蓄積されます。MPI 待機は、MPI ランタイムが同期化を待機している間に蓄積され、待機が CPU 時間ないしスリーピングのいずれかを使用しているか、または作業が並列実行中であるがスレッドは CPU 上にスケジュールされていない場合に、蓄積されます。
Linux OS では、MPI 作業および MPI 待機は、プロセスがユーザーモードまたはシステムモードでアクティブになっている場合にのみ蓄積されます。MPI がビジーウェイトを行う必要があるものとして指定しないかぎり、Linux での MPI 待機は有用ではありません。
時間ベースのプロファイルが OpenMP プログラムで実行される場合は、OpenMP 作業および OpenMP 待機という 2 つの追加メトリックスが提供されます。
Solaris OS では、OpenMP 作業は作業が直列または並列に実行される場合に蓄積されます。OpenMP 待機は、OpenMP ランタイムが同期化を待機している場合に蓄積し、待機が CPU 時間かスリーピングを使用しているか、または作業は並行してなされるがスレッドが CPU 上でスケジュールされていない場合に蓄積します。
Linux OS では、OpenMP 作業および OpenMP 待機は、プロセスがユーザーモードまたはシステムモードでアクティブになっている場合にのみ蓄積されます。OpenMP でビジーウェイトを行う必要があるものとして指定しないかぎり、Linux での OpenMP は有用ではありません。
ハードウェアカウンタは、キャッシュミス、キャッシュストールサイクル、浮動小数点演算、分岐予測ミス、CPU サイクル、および実行対象命令といったイベントの追跡に使用されます。ハードウェアカウンタオーバーフローのプロファイルでは、LWP が動作している CPU の特定のハードウェアカウンタがオーバーフローしたときに、コレクタはプロファイルパケットを記録します。この場合、そのカウンタはリセットされ、カウントを続行します。プロファイルパケットには、オーバーフロー値とカウンタタイプが入っています。
各種の CPU ファミリが 2 潤オ 18 個の同時ハードウェアカウンタレジスタをサポートしています。コレクタは、複数のレジスタ上でデータを収集できます。コレクタではレジスタごとに、オーバーフローを監視するカウンタの種類を選択し、カウンタのオーバーフロー値を設定することができます。ハードウェアカウンタには、任意のレジスタを使用できるものと、特定のレジスタしか使用できないものがあります。このことは、1 つの実験であらゆるハードウェアカウンタの組み合わせを選択できるわけではないことを意味します。
パフォーマンスアナライザは、ハードウェアカウンタのオーバーフロープロファイルデータをカウントメトリックスに変換します。循環型のカウンタの場合、報告されるメトリックスは時間に変換されます。非循環型のカウンタの場合は、イベントの発生回数になります。複数の CPU を搭載したマシンの場合、メトリックスの変換に使用されるクロック周波数が個々の CPU のクロック周波数の調和平均となります。プロセッサのタイプごとに専用のハードウェアカウンタセットがあり、またハードウェアカウンタの数が多いため、ハードウェアカウンタメトリックスはここに記載していません。次の項で、どのような種類のハードウェアカウンタがあるかについて調べる方法を説明します。
ハードウェアカウンタの用途の 1 つは、CPU に出入りする情報フローに伴う問題を診断することです。たとえば、キャッシュミス回数が多いということは、プログラムを再構成してデータまたはテキストの局所性を改善するか、キャッシュの再利用を増やすことによってプログラムのパフォーマンスを改善できることを意味します。
ハードウェアカウンタはほかのカウンタと関連する場合があります。たとえば、分岐予測ミスが発生すると、間違った命令が命令キャッシュに読み込まれ、これらの命令を正しい命令と置換しなければならなくなるため、分岐予測ミスと命令キャッシュミスが関連付けられることがよくあります。置換により、命令キャッシュミス、命令変換索引バッファー (Instruction Translation Look aside Buffer、ITLB) ミス、またはページフォルトが発生する可能性があります。
ハードウェアカウンタのオーバーフローは、イベントを発生させて対応するイベントのカウンタをオーバーフローにした命令のあとに、1 つ以上の命令で実現される傾向があります。これは「滑り止め」と呼ばれ、カウンタオーバーフローのプロファイルを解釈しにくくする可能性があります。原因となる命令を正確に識別するためのハードウェアサポートがないと、候補の原因となる命令の適切なバックトラッキング検索が行われる場合があります。
そのようなバックトラッキングが収集中にサポートされて指定されると、ハードウェアカウンタプロファイルパケットにはさらに、ハードウェアカウンタイベントに適した候補の、メモリー参照命令の PC (プログラムカウンタ) と EA (有効アドレス) が組み込まれます。解析中の以降の処理は、候補のイベント PC と EA を有効にするのに必要です。このメモリー参照イベントに関する追加情報により、データ空間プロファイリングと呼ばれるさまざまなデータ指向解析が容易になります。バックトラッキングは、Oracle Solaris オペレーティングシステムを実行している SPARC ベースのプラットフォームでのみサポートされます。
候補のイベント PC および EA のバックトラッキングと記録は、時間プロファイルに対しても指定できますが、解釈しにくい場合があります。
ハードウェアカウンタはプロセッサ固有であるため、どのカウンタを利用できるかは、使用しているプロセッサによって異なります。パフォーマンスツールには、よく使われると考えられるいくつかのカウンタの別名が用意されています。コレクタから特定システム上で利用できるハードウェアカウンタの一覧を取り出すには、引数を付けないで collect をそのシステム上の端末ウィンドウに入力します。プロセッサとシステムがハードウェアカウンタプロファイルをサポートしている場合、collect コマンドは、ハードウェアカウンタに関する情報が入った 2 つのリストを出力します。最初のリストには一般的な名称に別名が設定されたハードウェアカウンタが含まれ、2 番目のリストには raw ハードウェアカウンタが含まれます。パフォーマンスカウンタサブシステムも collect コマンドも特定システムのカウンタの名前を知らない場合、各リストは空になります。ただしほとんどの場合、カウンタは数値で指定できます。
次に、カウンタリストに含まれるエントリの表示例を示します。別名が設定されたカウンタがリストの最初に表示され、続いて raw ハードウェアカウンタリストが表示されます。この例の出力における各行は、印刷用の形式になっています。
Aliased 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 など) を提供します。
タイプ情報の最初のワードが、
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 ハードウェアカウンタリストに含まれる情報は、別名設定されたハードウェアカウンタリストに含まれる情報のサブセットです。raw ハードウェアカウンタリスト内の各行には、cpu-track(1) によって使用された内部カウンタ名、そのカウンタを使用できるレジスタ番号 (単数または複数)、デフォルトのオーバーフロー値、およびカウンタ単位が含まれており、カウンタ単位は CPU-cycles か Events です。
カウンタがプログラムの実行に関連のないイベントを測定する場合、タイプ情報の最初のワードは not-program-related になります。そのようなカウンタの場合、プロファイリングで呼び出しスタックが記録されませんが、その代わりに、擬似関数 collector_not_program_related で使用された時間が示されます。スレッドと LWP ID は記録されますが、意味がありません。
raw カウンタのデフォルトのオーバーフロー値は 1000003 です。この値はほとんどの raw カウンタで最適でないため、raw カウンタを指定する際にオーバーフロー値を指定する必要があります。
マルチスレッドプログラムでは、たとえば 1 つのスレッドによってデータがロックされていると、別のスレッドがそのアクセス待ちになることがあります。このため、複数のスレッドが実行するタスクの同期を取るために、プログラムの実行に遅延が生じることがあります。これらのイベントは同期遅延イベントと呼ばれ、Solaris または pthread のスレッド関数の呼び出しをトレースすることによって収集されます。同期遅延イベントを収集して記録するプロセスを同期待ちトレースと言います。また、ロック待ちに費やされる時間を待ち時間と言います。
ただし、イベントが記録されるのは、その待ち時間がしきい値 (ミリ秒単位) を超えた場合だけです。しきい値 0 は、待ち時間に関係なく、あらゆる同期遅延イベントをトレースすることを意味します。デフォルトでは、同期遅延なしにスレッドライブラリを呼び出す測定試験を実施して、しきい値を決定します。こうして決定された場合、しきい値は、それらの呼び出しの平均時間に任意の係数 (現在は 6) を乗算して得られた値です。この方法によって、待ち時間の原因が本当の遅延ではなく、呼び出しそのものにあるイベントが記録されないようになります。この結果として、同期イベント数がかなり過小評価される可能性がありますが、データ量は大幅に少なくなります。
同期トレースは Java プログラムに対してはサポートされていません。
表 2–2 同期待ちトレースメトリックス
メトリック |
定義 |
---|---|
待ち時間が所定のしきい値を超えたときの同期ルーチン呼び出し回数。 |
|
所定のしきい値を超えた総待ち時間。 |
この情報から、関数またはロードオブジェクトが頻繁にブロックされるかどうか、または同期ルーチンを呼び出したときの待ち時間が異常に長くなっているかどうかを調べることができます。同期待ち時間が大きいということは、スレッド間の競合が発生していることを示します。競合は、アルゴリズムの変更、具体的には、ロックする必要があるデータだけがスレッドごとにロックされるように、ロックを構成し直すことで減らすことができます。
正しく管理されていないメモリー割り当て関数やメモリー割り当て解除関数を呼び出すと、データの使い方の効率が低下し、プログラムパフォーマンスが低下する可能性があります。ヒープトレースでは、C 標準ライブラリメモリー割り当て関数 malloc、realloc、valloc、memalign、および割り当て解除関数 free で割り込み処理を行うことによって、コレクタはメモリーの割り当てと割り当て解除の要求をトレースします。mmap への呼び出しはメモリー割り当てとして扱われ、これによって Java メモリー割り当てのヒープトレースイベントを記録することが可能になります。Fortran 関数 allocate および deallocate は C 標準ライブラリ関数を呼び出すため、これらのルーチンは間接的にトレースされます。
Java プログラムのヒーププロファイリングはサポートされません。
表 2–3 メモリー割り当て (ヒープトレース) メトリックス
メトリック |
定義 |
---|---|
割り当て |
メモリー割り当て関数の呼び出し回数。 |
割り当てバイト数 |
メモリー割り当て関数の呼び出しごとに割り当てられるバイト数の合計。 |
リーク |
対応するメモリー割り当て解除関数が存在しなかったメモリー割り当て関数の呼び出し回数。 |
リークバイト数 |
割り当てられたが割り当て解除されなかったバイト数。 |
ヒープトレースデータの収集は、プログラム内のメモリーリークを特定したり、メモリーの割り当てが不十分な場所を見つける上で役立ちます。
dbx デバッグツールなどで使用されることの多い、メモリーリークの別の定義では、メモリーリークとは、プログラムのデータ空間内のいずれかを指しているポインタを持たない、動的に割り当てられるメモリーブロックです。ここで使用されているリークの定義にはこの代替定義が含まれますが、ポインタが存在するメモリーも含まれます。
コレクタは、Message Passing Interface (MPI) ライブラリの呼び出しの際のデータを収集できます。
MPI トレースは、オープンソースの VampirTrace 5.5.3 リリースを使用して実装されます。これは次の VampirTrace 環境変数を認識します。
これらの変数については、Technische Universität Dresden Web サイトにある『Vampirtrace User Manual』を参照してください。
バッファーの制限に達したあとに発生する MPI イベントはトレースファイルに書き込まれないため、トレースが不完全になります。
この制限を撤廃してアプリケーションのトレースを完全なものにするには、VT_MAX_FLUSHES 環境変数を 0 に設定します。この設定を行うと、MPI API トレースコレクタは、バッファーがいっぱいになるたびにバッファーをディスクにフラッシュします。
バッファーのサイズを変更するには、VT_BUFFER_SIZE 環境変数を設定します。この変数の最適値は、トレース対象のアプリケーションによって異なります。小さな値を設定すると、アプリケーションに利用できるメモリーは増えますが、MPI API トレースコレクタによるバッファーのフラッシュが頻繁に行われるようになります。このようなバッファーのフラッシュによって、アプリケーションの動作が大幅に変化する可能性があります。その一方、大きな値 (2G など) を設定すると、MPI API トレースコントローラによるバッファーのフラッシュは最小限に抑えられますが、アプリケーションに使用できるメモリーは少なくなります。バッファーやアプリケーションデータを保持するために十分なメモリーを利用できない場合、アプリケーションの一部がディスクにスワップされて、アプリケーションの動作が大幅に変化する可能性があります。
MPI_Abort |
MPI_Accumulate |
MPI_Address |
MPI_Allgather |
MPI_Allgatherv |
MPI_Allreduce |
MPI_Alltoall |
MPI_Alltoallv |
MPI_Alltoallw |
MPI_Attr_delete |
MPI_Attr_get |
MPI_Attr_put |
MPI_Barrier |
MPI_Bcast |
MPI_Bsend |
MPI_Bsend-init |
MPI_Buffer_attach |
MPI_Buffer_detach |
MPI_Cancel |
MPI_Cart_coords |
MPI_Cart_create |
MPI_Cart_get |
MPI_Cart_map |
MPI_Cart_rank |
MPI_Cart_shift |
MPI_Cart_sub |
MPI_Cartdim_get |
MPI_Comm_compare |
MPI_Comm_create |
MPI_Comm_dup |
MPI_Comm_free |
MPI_Comm_group |
MPI_Comm_rank |
MPI_Comm_remote_group |
MPI_Comm_remote_size |
MPI_Comm_size |
MPI_Comm_split |
MPI_Comm_test_inter |
MPI_Dims_create |
MPI_Errhandler_create |
MPI_Errhandler_free |
MPI_Errhandler_get |
MPI_Errhandler_set |
MPI_Error_class |
MPI_Error_string |
MPI_File_close |
MPI_File_delete |
MPI_File_get_amode |
MPI_File_get_atomicity |
MPI_File_get_byte_offset |
MPI_File_get_group |
MPI_File_get_info |
MPI_File_get_position |
MPI_File_get_position_shared |
MPI_File_get_size |
MPI_File_get_type_extent |
MPI_File_get_view |
MPI_File_iread |
MPI_File_iread_at |
MPI_File_iread_shared |
MPI_File_iwrite |
MPI_File_iwrite_at |
MPI_File_iwrite_shared |
MPI_File_open |
MPI_File_preallocate |
MPI_File_read |
MPI_File_read_all |
MPI_File_read_all_begin |
MPI_File_read_all_end |
MPI_File_read_at |
MPI_File_read_at_all |
MPI_File_read_at_all_begin |
MPI_File_read_at_all_end |
MPI_File_read_ordered |
MPI_File_read_ordered_begin |
MPI_File_read_ordered_end |
MPI_File_read_shared |
MPI_File_seek |
MPI_File_seek_shared |
MPI_File_set_atomicity |
MPI_File_set_info |
MPI_File_set_size |
MPI_File_set_view |
MPI_File_sync |
MPI_File_write |
MPI_File_write_all |
MPI_File_write_all_begin |
MPI_File_write_all_end |
MPI_File_write_at |
MPI_File_write_at_all |
MPI_File_write_at_all_begin |
MPI_File_write_at_all_end |
MPI_File_write_ordered |
MPI_File_write_ordered_begin |
MPI_File_write_ordered_end |
MPI_File_write_shared |
MPI_Finalize |
MPI_Gather |
MPI_Gatherv |
MPI_Get |
MPI_Get_count |
MPI_Get_elements |
MPI_Get_processor_name |
MPI_Get_version |
MPI_Graph_create |
MPI_Graph_get |
MPI_Graph_map |
MPI_Graph_neighbors |
MPI_Graph_neighbors_count |
MPI_Graphdims_get |
MPI_Group_compare |
MPI_Group_difference |
MPI_Group_excl |
MPI_Group_free |
MPI_Group_incl |
MPI_Group_intersection |
MPI_Group_rank |
MPI_Group_size |
MPI_Group_translate_ranks |
MPI_Group_union |
MPI_Ibsend |
MPI_Init |
MPI_Init_thread |
MPI_Intercomm_create |
MPI_Intercomm_merge |
MPI_Irecv |
MPI_Irsend |
MPI_Isend |
MPI_Issend |
MPI_Keyval_create |
MPI_Keyval_free |
MPI_Op_create |
MPI_Op_free |
MPI_Pack |
MPI_Pack_size |
MPI_Probe |
MPI_Put |
MPI_Recv |
MPI_Recv_init |
MPI_Reduce |
MPI_Reduce_scatter |
MPI_Request_free |
MPI_Rsend |
MPI_rsend_init |
MPI_Scan |
MPI_Scatter |
MPI_Scatterv |
MPI_Send |
MPI_Send_init |
MPI_Sendrecv |
MPI_Sendrecv_replace |
MPI_Ssend |
MPI_Ssend_init |
MPI_Start |
MPI_Startall |
MPI_Test |
MPI_Test_cancelled |
MPI_Testall |
MPI_Testany |
MPI_Testsome |
MPI_Topo_test |
MPI_Type_commit |
MPI_Type_contiguous |
MPI_Type_extent |
MPI_Type_free |
MPI_Type_hindexed |
MPI_Type_hvector |
MPI_Type_indexed |
MPI_Type_lb |
MPI_Type_size |
MPI_Type_struct |
MPI_Type_ub |
MPI_Type_vector |
MPI_Unpack |
MPI_Wait |
MPI_Waitall |
MPI_Waitany |
MPI_Waitsome |
MPI_Win_complete |
MPI_Win_create |
MPI_Win_fence |
MPI_Win_free |
MPI_Win_lock |
MPI_Win_post |
MPI_Win_start |
MPI_Win_test |
MPI_Win_unlock |
表 2–4 MPI トレースメトリックス
メトリック |
定義 |
---|---|
MPI 送信数 |
開始された MPI ポイントツーポイント送信数 |
MPI 送信バイト数 |
MPI で送信されるバイト数 |
MPI 受信数 |
完了した MPI ポイントツーポイント受信数 |
MPI 受信バイト数 |
MPI で受信されるバイト数 |
MPI 時間 |
MPI 関数へのすべての呼び出しにかかった時間 |
そのほかの MPI イベント |
2 点間のメッセージの送受信を行わない MPI 関数の呼び出しの数 |
MPI 時間は MPI 関数でかかった LWP 時間の合計です。MPI 状態の時間も収集される場合、MPI_Init および MPI_Finalize 以外のすべての MPI 関数については、MPI 作業時間と MPI 待機時間の合計が MPI 作業時間にほぼ等しくなるはずです。Linux では、MPI 待機および MPI 作業はユーザー CPU 時間とシステム CPU 時間の合計に基づきますが、MPI 時間は実際の時間に基づくため、数値は一致しません。
MPI のバイトおよびメッセージのカウントは、現在のところ 2 点間のメッセージについてのみ収集され、集合的な通信機能に関しては記録されません。MPI 受信バイト数は、すべてのメッセージで実際に受信したバイト数をカウントします。MPI 送信バイト数は、すべてのメッセージで実際に送信したバイト数をカウントします。MPI 送信数は送信したメッセージの数をカウントし、MPI 受信数は受信したメッセージの数をカウントします。
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 つのソースコード行にマップされ、その命令に割り当てられたメトリックスも同じソースコード行に対応付けられます。この仕組みについての詳細は、第 6 章パフォーマンスアナライザとそのデータについてを参照してください。
メトリックスは、ソースコードと命令のほかに、より上位のオブジェクト (関数とロードオブジェクト) にも対応付けられます。呼び出しスタックには、プロファイルが取られたときに記録された命令アドレスに達するまでに行われた、一連の関数呼び出しに関する情報が含まれます。パフォーマンスアナライザは、この呼び出しスタックを使用し、プログラム内の各関数のメトリックスを計算します。こうして得られたメトリックスを関数レベルのメトリックスといいます。
パフォーマンスアナライザが計算する関数レベルのメトリックスには、排他的メトリックス、包括的メトリックス、および属性メトリックスの 3 種類があります。
関数の排他的メトリックスは、関数自体の内部で発生するイベントにより計算されます。これには、ほかの関数の呼び出しから発生したメトリックスは含まれません。
包括的メトリックスは、関数の内部で発生するイベントと、その関数から呼び出す関数により計算されます。これには、ほかの関数の呼び出しにから発生したメトリックが含まれます。
属性メトリックスは、どれだけの包括的メトリックが、別の関数からの呼び出しによるものか、または別の関数の呼び出しによるものかを示します。つまり、属性メトリックスは別の関数に起因するメトリックです。
呼び出しスタックの一番下にのみ現れる関数 (リーフ関数) では、その関数の排他的および包括的メトリックスは同じになります。
排他的および包括的メトリックスは、ロードオブジェクトについても計算されます。ロードオブジェクトの排他的メトリックスは、そのロードオブジェクト内の全関数の関数レベルのメトリックスを集計することによって計算されるメトリックスです。ロードオブジェクトの包括的メトリックスは、関数に対するのと同じ方法で計算されるメトリックスです。
関数の排他的および包括的メトリックスは、その関数を通るすべての記録経路に関する情報を提供します。属性メトリックスは、関数を通る特定の経路に関する情報を提供します。その情報は、どれだけのメトリックが特定の関数呼び出しが原因で発生したかを示します。その呼び出しに関係する 2 つの関数は、呼び出し元および呼び出し先として表されます。呼び出しツリー内のそれぞれの関数では次のことが言えます。
関数の呼び出し元の属性メトリックスは、その関数の包括的メトリックのうち、各呼び出し元からの呼び出しが原因になっているメトリックスを示します。呼び出し元の属性メトリックスを合計したものが、関数の包括的メトリックです。
関数の呼び出し先の属性メトリックスは、その関数の包括的メトリックのうち、各呼び出し先への呼び出しが原因になっているメトリックスを示します。それらの合計に、その関数の排他的メトリックを加えたものが、その関数の包括的メトリックに等しくなります。
メトリックス間の関係は、次の等式で表すことができます。
呼び出し元または呼び出し先の属性メトリックスと包括的メトリックスを比較すると、さらに情報が得られます。
呼び出し元の属性メトリックとその包括的メトリックの差は、ほかの関数への呼び出しおよびその呼び出し元自体の動作が原因で発生したメトリックを示します。
呼び出し先の属性メトリックとその包括的メトリックの差は、その呼び出し先の包括的メトリックのうち、ほかの関数からのその呼び出し先への呼び出しが原因で発生したメトリックを示します。
プログラムのパフォーマンス改善が可能な場所を見つける方法には、次のものがあります。
包括的メトリックスを使用して、プログラム内のどの呼び出しシーケンスが大きなメトリック値の原因になっているかを判定します。
属性メトリックスを使用して、大きなメトリック値の原因になっている特定の 1 つまたは複数の関数に対する呼び出しシーケンスをトレースします。
図 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 の包括的メトリックスと排他的メトリックスの差に等しくなり、それぞれが関数 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 は関数 G を呼び出さないためです。
このプログラムの擬似コードを次に示します。
main() { A(); /Do 2 units of work;/ B(); } A() { C(10); } B() { C(7.5); /Do 5 units of work;/ C(7.5); } C(arg) { /Do a total of "arg" units of work, with 20% done in C itself, 40% done by calling E, and 40% done by calling F./ }
直接または間接のどちらの場合も、再帰関数呼び出しがあると、メトリックスの計算が複雑になります。パフォーマンスアナライザは、関数の呼び出しごとのメトリックスではなく、関数全体としてのメトリックスを表示します。このため、一連の再帰的な呼び出しのメトリックスを 1 つのメトリックに要約する必要があります。この要約によって、呼び出しスタックの最後の関数 (リーフ関数) から計算される排他的メトリックスが影響を受けることはありませんが、包括的および属性メトリックスは影響を受けます。
包括的メトリックスは、イベントのメトリックと呼び出しスタック内の関数の包括的メトリックを合計することによって計算されます。再帰呼び出しスタックでメトリックが複数回カウントされないようにするには、イベントのメトリックが、同じ関数の包括的メトリックに 1 回だけ加算されるようにします。
属性メトリックスは、包括的メトリックスから計算されます。もっとも簡単な再帰では、再帰関数は、それ自身ともう 1 つの関数 (呼び出しを開始する関数) の 2 つの呼び出し元を持ちます。最後の呼び出しですべての動作を終えた場合、再帰関数の包括的メトリックの原因になるのは、その再帰関数であり、呼び出しを開始した関数ではありません。この現象が起こるのは、メトリックが複数回カウントされるのを避けるために、再帰関数の上位の呼び出しすべてについて、包括的メトリックがゼロと見なされるためです。ただし、呼び出しを開始した関数は、再帰呼び出しであるために、呼び出し先としての再帰関数の包括的メトリックの一部の原因になります。