パフォーマンスアナライザは、コレクタが収集したイベントデータを読み取り、そのデータをパフォーマンスメトリックスに変換します。メトリックスは、ターゲットプログラムの構造内の、命令、ソース行、関数、ロードオブジェクトなどのさまざまな要素について計算されます。タイムスタンプ、スレッド ID、LWP ID、および CPU ID を含むヘッダーに加えて、各イベントについて次の 2 つの部分からなるデータが収集され、記録されます。
メトリックスの計算に使用されるイベント固有のデータ
プログラム構造へのメトリックスの関連付けに使用されるアプリケーションの呼び出しスタック
プログラム構造にメトリックスを関連付ける処理は、常に簡単にできるとはかぎりません。これは、コンパイラによって、コードの挿入や変換、最適化が行われるためです。この章では、この処理を説明するとともに、パフォーマンスアナライザの表示にそのことがどのように反映されるのかという問題を取り上げます。
この章では、次の内容について説明します。
データ収集の実行による出力は実験であり、さまざまな内部ファイルとサブディレクトリを持つディレクトリとしてファイルシステム内に格納されます。
すべての実験には、次の 3 つのファイルが含まれています。
ログファイル (log.xml)。どのようなデータが収集されたか、各種コンポーネントのバージョン、ターゲットが存続している間の各種イベントのレコード、およびターゲットのワードサイズなどに関する情報が含まれている XML ファイル。
マップファイル (map.xml)。どのようなロードオブジェクトがターゲットのアドレス空間に読み込まれたかに関する時間従属情報と、それらのロードオブジェクトが読み込まれたか読み込み解除された時間を記録した XML ファイル。
オーバービューファイル。実験内のあらゆる標本点で記録された使用情報を含むバイナリファイル。
また、実験にはプロセスが存続している間のプロファイルイベントを表すバイナリデータファイルがあります。各データファイルには、「パフォーマンスメトリックスの解釈」で説明しているように、一連のイベントがあります。データの種類ごとに個別のファイルを使用しますが、各ファイルはターゲット内のすべての LWP で共有されます。
時間ベースのプロファイルまたはハードウェアカウンタオーバーフローのプロファイルの場合、データはクロック刻みまたはカウンターオーバーフローによって呼び出されたシグナルハンドラに書き込まれます。同期トレース、ヒープトレース、MPI トレース、または OpenMP トレースの場合は、通常のユーザー呼び出しルーチンで LD_PRELOAD 環境変数により割り込み処理される libcollector ルーチンからデータが書き込まれます。そのような割り込み処理ルーチンは部分的にデータレコードを記入したあと、通常のユーザー呼び出しルーチンを呼び出し、ルーチンが復帰したときにデータレコードの残りの部分を記入し、データファイルにレコードを書き込みます。
すべてのデータファイルはメモリーマップされ、ブロック単位で書き込まれます。レコードは常に有効なレコード構造を持つように記入されるので、実験は書き込み中に読み取ることができます。LWP 間の競合とシリアル化を最小限にするために、バッファー管理戦略が設計されています。
オプションで、notes というファイル名の ASCII ファイルを実験に含めることができます。このファイルは、collect コマンドに -C comment 引数を使用すると、自動的に作成されます。実験の作成後、ファイルを手動で編集または作成できます。ファイルの内容は、実験のヘッダーの先頭に付加されます。
各実験には archives ディレクトリがあり、このディレクトリには、map.xml ファイル内で参照されている各ロードオブジェクトについて記述したバイナリファイルがあります。これらのファイルは、データ収集の終了時に実行される er_archive ユーティリティーによって作成されます。プロセスが異常終了すると、er_archive ユーティリティーが呼び出されない場合があります。その場合、アーカイブファイルは最初に実験に対して er_print ユーティリティーまたはアナライザを呼び出したときに書き込まれます。
派生プロセスは、その実験データを親プロセスの実験ディレクトリのサブディレクトリに書き込みます。
これらの新しい実験には、次のようにそれぞれの系統を示す名前が付けられます。
作成者の実験名にアンダースコアが付加される。
fork には f、exec には x、そのほかの派生実験には c のコード文字が追加される。
コード文字のあとに、fork または exec のインデックスを示す数字が追加される。この数字は、プロセスが正常に開始されたかどうかに関係なく適用される。
実験接尾辞 .er を付加して実験名が完成する。
たとえば親プロセスの実験名が test.1.er の場合、3 回目の fork の呼び出しで作成された子プロセスの実験は test.1.er/_f3.er となります。この子プロセスが新しいイメージを実行した場合、対応する実験名は test.1.er/_f3_x1.er となります。派生実験は親の実験と同じファイルから構成されていますが、派生実験を持たず (すべての派生は親の実験内のサブディレクトリで表される)、アーカイブサブディレクトリを持っていません (すべてのアーカイブが親の実験内へ行われる)。
ターゲットが動的な関数を作成する実験には、map.xml ファイル内に動的な関数を記述する追加レコードと、動的な関数の実際の命令のコピーを含む追加ファイル dyntext があります。動的な関数の注釈付き逆アセンブリを生成するには、このコピーが必要です。
Java 実験の map.xml ファイル内には、その内部処理用に JVM ソフトウェアで作成された動的な関数用と、ターゲット Java メソッドの動的にコンパイルされた (HotSpot) バージョン用の追加レコードがあります。
さらに、Java 実験には、呼び出されたすべての Java ユーザークラスの情報を含む JAVA_CLASSES ファイルがあります。
Java トレースデータは、libcollector.so の一部である JVMTI エージェントを使用して記録されます。エージェントは、記録されたトレースイベントへマップされるイベントを受け取ります。このエージェントは、JAVA_CLASSES ファイルの書き込みに使用するクラスの読み込みと HotSpot のコンパイルのためのイベント、および map.xml ファイル内の Java でコンパイルされたメソッドレコードも受信します。
実験を記録する方法には、次の 3 つがあります。
collect コマンド
dbx によるプロセスの生成
dbx による実行中のプロセスからの実験の作成
アナライザ GUI の「パフォーマンスコレクタ」ウィンドウでは、collect 実験が実行されます。
collect コマンドを使用して実験を記録する場合、collect ユーティリティーは実験ディレクトリを作成し、libcollector.so およびそのほかの libcollector モジュールがターゲットのアドレス空間にあらかじめ読み込まれるように LD_PRELOAD 環境変数を設定します。collect ユーティリティーは、その後、libcollector.so に実験名を知らせるための環境変数とデータ収集オプションを設定し、ターゲットをその上で実行します。
libcollector.so および関連モジュールが、すべての実験ファイルを書き込みます。
データ収集を有効にした状態で dbx を使用してプロセスを起動すると、dbx は実験ディレクトリも作成し、libcollector.so が事前に読み込まれるようにします。dbx は最初の命令の前のブレークポイントでプロセスを停止し、次にデータ収集を開始するために libcollector.so 内の初期化ルーチンを呼び出します。
Java 実験データが dbx で収集できないのは、dbx がデバッグのために Java Virtual Machine Debug Interface (JVMDI) エージェントを使用し、そのエージェントがデータの収集に必要な Java Virtual Machine Tools Interface (JVMTI) エージェントと共存できないからです。
dbx を使用して実行中のプロセスで実験を開始すると、dbx は実験ディレクトリを作成しますが、LD_PRELOAD 環境変数を使用できません。dbx は対話式関数呼び出しをターゲット内に行なって libcollector.so を開き、次にプロセスを作成する場合と同様に libcollector.so の初期化ルーチンを呼び出します。データは、collect による実験の場合と同様に libcollector.so とそのモジュールによって書き込まれます。
プロセスが開始したときに libcollector.so はターゲットアドレス空間になかったので、ユーザー呼び出し可能関数 (同期トレース、ヒープトレース、MPI トレース) に対する割り込み処理に依存するデータ収集は機能しない場合があります。一般に、シンボルはすでに基礎的な関数に分解されているので、割り込み処理は行えません。さらに、派生プロセスの次も割り込み処理に依存し、実行中プロセスで dbx により作成された実験に対して適切に機能しません。
dbx でプロセスを開始する前、または dbx で実行中プロセスに接続する前に明示的に libcollector.so を事前読み込みした場合は、トレースデータを収集できます。
各イベントのデータには、高分解能のタイムスタンプ、スレッド ID、LWP ID、プロセッサ ID が含まれます。パフォーマンスアナライザでは、この最初の 3 つのデータを使用して、時間、スレッド、LWP、または CPU によるメトリックスのフィルタ処理を実行できます。プロセッサ ID については、getcpuid(2) のマニュアルページを参照してください。getcpuid を利用できないシステムでのプロセッサ ID は -1 であり、Unknown にマップされます。
各イベントでは、共通データ以外に、以降の節で説明する固有の raw データが生成されます。これらの節ではまた、raw データから得られるメトリックスの精度と、データ収集がメトリックスに及ぼす影響についても説明しています。
時間ベースのプロファイリングのイベント固有のデータは、プロファイル間隔カウント値の配列で構成されています。Solaris OS の場合は、間隔カウンタが提供されます。プロファイル間隔の最後で適切な間隔カウンタが 1 増分され、別のプロファイル信号がスケジューリングされます。この配列が記録され、リセットされるのは、Solaris LWP スレッドが CPU ユーザーモードに入った場合だけです。配列のリセット時には、ユーザー CPU 状態の配列要素が 1 に設定され、ほかの全状態の配列要素が 0 に設定されます。配列データが記録されるのは、配列がリセットされる前にユーザーモードに入るときです。したがって、配列には、Solaris LWP ごとにカーネルが保持する 10 個のマイクロステートのそれぞれについて、ユーザーモードに前回入って以降の各マイクロステートのカウントの累計値が含まれます。Linux OS ではマイクロステートは存在せず、利用できる間隔カウンタはユーザー CPU 時間だけです。
呼び出しスタックは、データと同時に記録されます。プロファイル間隔の最後で Solaris LWP がユーザーモードでない場合は、LWP またはスレッドが再びユーザーモードになるまで、呼び出しスタックの内容は変わりません。すなわち、呼び出しスタックには、各プロファイル間隔の最後のプログラムカウンタの位置が常に正確に記録されます。
表 6–1 に、各マイクロステートとメトリックスの、Solaris OS における対応関係を示します。
表 6–1 カーネルのマイクロステートとメトリックスとの対応関係
カーネルのマイクロステート |
内容の説明 |
メトリック名 |
---|---|---|
LMS_USER |
ユーザーモードで動作 |
ユーザー CPU 時間 |
LMS_SYSTEM |
システムコールまたはページフォルトで動作 |
システム CPU 時間 |
LMS_TRAP |
上記以外のトラップで動作 |
システム CPU 時間 |
LMS_TFAULT |
ユーザーテキストページフォルトでスリープ |
テキストページフォルト時間 |
LMS_DFAULT |
ユーザーデータページフォルトでスリープ |
データページフォルト時間 |
LMS_KFAULT |
カーネルページフォルトでスリープ |
ほかの待ち時間 |
LMS_USER_LOCK |
ユーザーモードロック待ちのスリープ |
ユーザーロック時間 |
LMS_SLEEP |
ほかの理由によるスリープ |
ほかの待ち時間 |
LMS_STOPPED |
停止 (/proc、ジョブ制御、lwp_stop のいずれか) |
ほかの待ち時間 |
LMS_WAIT_CPU |
CPU 待ち |
CPU 待ち時間 |
このため、ほかの統計的な標本収集手法と同様に、あらゆる誤差の影響を受けます。プログラムの実行時間が非常に短い場合は、少数のプロファイルパケットしか記録されず、多くのリソースを消費するプログラム部分が、呼び出しスタックに反映されないことがあります。このため、目的の関数またはソース行について数百のプロファイルパケットを蓄積するのに十分な時間または十分な回数に渡って、プログラムを実行するようにしてください。
統計的な標本収集の誤差のほかに、データの収集?関連付け方法、システムにおけるプログラムの実行の進み具合を原因とする誤差もあります。次に示す環境などでは、タイミングメトリックスでデータに不正確さやひずみが生じる可能性があります。
Solaris LWP または Linux スレッドが作成されるとき、最初のプロファイルパケットが記録されるまでの時間はプロファイル間隔より短いですが、プロファイル間隔全体の時間が、最初のプロファイルパケットに記録されるマイクロステートに帰せられます。多数の LWP またはスレッドが作成される場合、誤差はプロファイル間隔の数倍になることがあります。
Solaris LWP または Linux スレッドが破壊されるとき、最後のプロファイルパケットが記録されてから、少し時間が費やされます。多数の LWP またはスレッドが破壊される場合、誤差はプロファイル間隔の数倍になることがあります。
プロファイル間隔中に LWP またはスレッドの再スケジューリングが行われることがあります。このため、LWP について記録された状態に、プロファイル間隔の大半を費やしたマイクロステートが反映されないことがあります。Solaris LWP または Linux スレッドの方がそれらを実行するプロセッサの個数より多いほど、誤差は大きくなる可能性があります。
プログラムがシステムクロックと相関関係を持つ形で動作することがあります。この場合、Solaris LWP または Linux スレッドが費やされた時間のごく一部を表す状態にあるときに、常にプロファイル間隔の時間切れになり、プログラムの特定部分について記録された呼び出しスタックの出現回数が実際より多くなります。マルチプロセッサシステムでは、プロファイルシグナルによって相関関係が生じる場合があります。プログラムの LWP を実行中にプロファイルシグナルによって中断されたプロセッサが、マイクロステートの記録時にトラップ CPU マイクロステートになる可能性があります。
カーネルは、プロファイル間隔の時間切れになったときにマイクロステート値を記録します。システムが過負荷状態の場合、この値に、プロセスの本当の状態が反映されないことがあります。Solaris OS では、この結果、トラップ CPU または CPU 待ちマイクロステート値が実際より大きくなることがあります。
システムクロックと外部ソースとの同期がとられている場合、プロファイルパケットに記録されるタイムスタンプはプロファイル間隔を反映しませんが、システムクロックに対して施された調整結果は組み込まれます。 システムクロック調整の結果、プロファイルパケットが失われたかのように見える可能性があります。その時間は通常数秒間であり、調整は一定の増分単位で行われます。
動作クロック周波数が動的に変わるマシンで記録される実験は、プロファイリングの不正確さが生じることがあります。
これらの不正確さのほかに、データ収集処理そのものが原因でタイミングメトリックスが不正確になります。記録はプロファイルシグナルによって開始されるため、プロファイルパケットの記録に費やされた時間が、プログラムのメトリックスに反映されることはありません。これは、相関関係の別の例です。記録に費やされたユーザー CPU 時間は、記録されるあらゆるマイクロステート値に配分されます。この結果、ユーザー CPU 時間のメトリックが実際より小さくなり、その他のメトリックスが実際より大きくなります。デフォルトのプロファイル間隔の場合、一般に、データの記録に費やされる時間は CPU 時間の 2、3% 未満です。
時間ベースの実験のプロファイリングで得られたタイミングメトリックスと、その他の方法で得られた時間を比較する場合は、次の点に注意する必要があります。
シングルスレッドアプリケーションの場合、1 つのプロセスについて記録された Solaris LWP または Linux スレッドの合計時間は、同じプロセスについて gethrtime (3C) によって返される値と比較して、誤差は数十分の 1 パーセントです。CPU 時間の場合は、同じプロセスについて gethrvtime(3C) によって返される値と比較して、数パーセント程度異なることがあります。負荷が大きい場合は、差がさらに大きくなることがあります。ただし、CPU 時間の差は規則的なひずみを表すものではなく、関数、ソース行などについて報告される相対時間に大きなひずみはありません。
Solaris OS の非結合スレッドを使用するマルチスレッドアプリケーションの場合、gethrvtime() によって返される値の差が無意味であることがあります。これは、gethrvtime() は LWP について値を返し、スレッドは LWP ごとに異なることがあるからです。
パフォーマンスアナライザの報告する LWP 時間が、vmstat の報告する時間とかなり異なることがあります。これは、vmstat が CPU 全体にまたがって集計した時間を報告するためです。たとえば、ターゲットプロセスの LWP 数が、そのプロセスが動作するシステムの CPU 数よりも多い場合、アナライザは、vmstat が報告する時間よりもずっと長い待ち時間を報告します。
パフォーマンスアナライザの「統計」タブと er_print 統計ディスプレイに表示されるマイクロステート時間値は、プロセスファイルシステムの /proc 使用報告に基づいており、この報告には、マイクロステートで費やされる時間が高い精度で記録されます。詳細は、proc (4) のマニュアルページを参照してください。これらのタイミング値と <Total> 関数 (プログラム全体を表す) のメトリックスを比較することによって、集計されたタイミングメトリックスのおおよその精度を知ることができます。ただし、「統計」タブに表示される値には、<Total> のタイミングメトリック値に含まれないそのほかの関連要素が含まれることがあります。その原因は、データ収集が一時停止される期間によるものです。
ユーザー CPU 時間とハードウェアカウンタサイクル時間は異なります。なぜなら、ハードウェアカウンタは、CPU モードがシステムモードへ切り替えられたときにオフにされるからです。詳細は、「トラップ」を参照してください。
コレクタは、スレッドライブラリ libthread.so 内の関数の呼び出し、またはリアルタイム拡張ライブラリ librt.so の呼び出しをトレースすることによって、同期遅延イベントを収集します。イベント固有のデータは、要求と許可 (トレース対象の呼び出しの始まりと終わり) の高分解能のタイムスタンプと、同期オブジェクト (要求された相互排他ロックなど) のアドレスとで構成されます。スレッド ID と LWP ID は、データが記録された時点での ID です。待ち時間は、要求時刻と許可時刻の時間差で示されます。記録されるイベントは、指定したしきい値を要求と許可の時間差が超えたものだけです。同期待ちトレースデータは、許可時に実験ファイルに記録されます。
遅延の原因となったイベントが完了しないかぎり、待ちスレッドがスケジューリングされている LWP がほかの作業を行うことはできません。この待ち時間は、「同期待ち時間」と「ユーザーロック時間」の両方に反映されます。同期遅延しきい値は短時間の遅延を排除するので、「ユーザーロック時間」が「同期待ち時間」よりも大きくなる可能性があります。
待ち時間は、データ収集のオーバーヘッドによってひずみます。そして、このオーバーヘッドは、収集されたイベントの個数に比例します。オーバーヘッドに費やされる待ち時間の一部は、イベント記録のしきい値を大きくすることによって最小化できます。
ハードウェアカウンタオーバーフローのプロファイルデータには、カウンタ ID とオーバーフロー値が含まれます。この値は、カウンタがオーバーフローするように設定されている値よりも大きくなることがあります。これは、オーバーフローが発生して、そのイベントが記録されるまでの間に命令が実行されるためです。この値は特に、浮動小数点演算やキャッシュミスなどのカウンタよりも、ずっと頻繁に増分されるサイクルカウンタや命令カウンタの場合に大きくなる可能性があります。イベント記録時の遅延はまた、呼び出しスタックとともに記録されたプログラムカウンタのアドレスが正確にオーバーフローイベントに対応しないことを意味します。詳細は、「ハードウェアカウンタオーバーフローの関連付け」を参照してください。また、「トラップ」も参照してください。トラップおよびトラップハンドラは、ユーザーの CPU 時間とサイクルカウンタによって報告される時間の間の、大きな相違の原因になることがあります。
動作クロック周波数が動的に変わるマシンで記録される実験では、サイクルベースのカウントから時間への変換で不正確さが生じます。
収集されるデータ量は、オーバーフロー値に依存します。選択した値が小さすぎると、次のような影響が出ることがあります。
データの収集に費やされる時間が、プログラムの実行時間のかなりの部分を占めることがあります。収集実行では、プログラムの実行ではなく、オーバーフローの処理とデータの書き込みに時間のかなりが費やされる場合があります。
カウントのかなりの部分の原因がデータ収集であることがあります。こうしたカウントは、コレクタ関数の collector_record_counters によるものです。この関数のカウントが大きい場合は、オーバーフロー値が小さすぎます。
データ収集によってプログラムの動作が変わることがあります。たとえば、キャッシュミスのデータの収集では、キャッシュミスの大半が、コレクタの命令のフラッシュとキャッシュからのデータのプロファイリング、プログラム命令とデータとの置き換えによるものです。プログラムで多くのキャッシュミスが発生するように見えますが、データ収集を行わなければキャッシュミスはごくわずかであった可能性があります。
コレクタは、メモリーの割り当てと割り当て解除の関数である malloc、realloc、memalign、free に割り込むことによって、これらの関数の呼び出しに関するトレースデータを記録します。メモリーを割り当てるときにこれらの関数を迂回するプログラムの場合、トレースデータは記録されません。別のメカニズムが使用されている Java メモリー管理では、トレースデータは記録されません。
トレース対象の関数は、さまざまなライブラリから読み込まれる可能性があります。パフォーマンスアナライザで表示されるデータは、読み込み対象の関数が属しているライブラリに依存することがあります。
短時間で大量のトレース対象関数を呼び出すプログラムの場合、プログラムの実行に要する時間が大幅に長くなることがあります。延びた時間は、トレースデータの記録に使用されます。
データ空間プロファイリングは、メモリ参照に対して使用されるハードウェアカウンタプロファイリングの拡張版です。ハードウェアカウンタプロファイリングでは、メトリックスをユーザー関数、ソース行、および命令に割り当てることができますが、参照されるデータオブジェクトに割り当てることはできません。デフォルトでは、コレクタはユーザー命令アドレスのみを取得します。データ空間プロファイリングが有効な場合、コレクタはデータアドレスも取得します。バックトラッキングとは、データ空間プロファイリングをサポートするパフォーマンス情報の取得に使用されるテクニックです。バックトラッキングが有効な場合、コレクタは、ハードウェアカウンタイベント前に発生したロード命名またはストア命令に戻って、そのイベントの原因になった可能性のある命令の候補を1つ見つけます。
データ空間プロファイリングを可能にするには、ターゲットは、-xhwcprof フラグと -xdebugformat=dwarf -g フラグを付けて SPARC アーキテクチャー用にコンパイルされた C プログラムである必要があります。さらに、収集されるデータは、メモリー関係のカウンタのハードウェアカウンタプロファイルでなければならず、カウンタ名の前に + 記号を付加する必要があります。パフォーマンスアナライザには、データ空間プロファイリング関係のタブとして、「データオブジェクト」タブと「データレイアウト」タブのほか、メモリーオブジェクト用の各種のタブが含まれています。
データ空間プロファイリングは、プロファイル間隔の前にプラス記号 (+) を付加することで、時間ベースのプロファイリングとともに実施できます。
引数なしで collect を実行すると、ハードウェアカウンタが一覧表示され、それらが、ロード関係なのか、ストア関係なのか、ロード-ストア関係なのかが示されます。「ハードウェアカウンタオーバーフローのプロファイルデータ」を参照してください。
MPI トレースは、VampirTrace データコレクタの修正版をベースにしています。詳細は、Technische Universität Dresden Web サイトにある『Vampirtrace User Manual』を参照してください。
呼び出しスタックは、プログラム内の命令を示す一連のプログラムカウンタ (Program Counter、PC) アドレスです。リーフ PC と呼ばれる最初の PC はスタックの一番下に位置し、次に実行される命令のアドレスを表します。次の PC はそのリーフ PC を含む関数の呼び出しアドレス、そして、その次の PC がその関数の呼び出しアドレスというようにして、これがスタックの先頭まで続きます。こうしたアドレスはそれぞれ、復帰アドレスと呼びます。呼び出しスタックの記録プロセスでは、プログラムスタックから復帰アドレスが取得されます。これは、スタックの展開と呼ばれています。 展開の失敗については、「不完全なスタック展開」を参照してください。
呼び出しスタック内のリーフ PC は、この PC が存在する関数にパフォーマンスデータの排他的メトリックスを割り当てるときに使用されます。スタック上の各 PC は、リーフ PC も含めて、その PC が存在する関数に包括的メトリックスを割り当てるために使用されます。
ほとんどの場合、記録された呼び出しスタック内の PC は、プログラムのソースコードに現れる関数に自然な形で対応しており、パフォーマンスアナライザが報告するメトリックスもそれらの関数に直接対応しています。しかし、プログラムの実際の実行は、単純で直観的なプログラム実行モデルと対応しないことがあり、その場合は、アナライザの報告するメトリックスが混乱を招くことがあります。こうした事例については、「プログラム構造へのアドレスのマッピング」を参照してください。
プログラムの実行でもっとも単純なものは、シングルスレッドプログラムが専用のロードオブジェクト内の関数を呼び出す場合です。
プログラムがメモリーに読み込まれて実行が開始されると、初期実行アドレス、初期レジスタセット、スタック (スクラッチデータの格納および関数の相互の呼び出し方法の記録に使用されるメモリー領域) からなるコンテキストが作成されます。初期アドレスは常に、あらゆる実行可能ファイルに組み込まれる _start() 関数の先頭位置になります。
プログラムを実行すると、たとえば関数呼び出しや条件文を表すことがある分岐命令があるまで、命令が順実行されます。分岐点では、分岐先が示すアドレスに制御が渡されて、そこから実行が続行されます。通常、分岐の次の命令は実行するようすでにコミットされており、この命令は分岐遅延スロット命令と呼ばれます。ただし、分岐命令には、この分岐遅延スロット命令の実行を無効にするものもあります。
呼び出しを表す命令シーケンスが実行されると、復帰アドレスがレジスタに書き込まれ、呼び出された関数の最初の命令から実行が続行されます。
ほとんどの場合は、この呼び出し先の関数に含まれる最初の数個の命令のどこかで、新しいフレーム (関数に関する情報を格納するためのメモリー領域) がスタックにプッシュされ、そのフレームに復帰アドレスが格納されます。復帰アドレスに使用されるレジスタは、呼び出された関数がほかの関数を呼び出すときに使用できます。関数から制御が戻されようとすると、スタックからフレームがポップされ、関数の呼び出し元のアドレスに制御が戻されます。
共有オブジェクト内の関数が別の共有オブジェクトの関数を呼び出す場合は、同じプログラム内の単純な関数の呼び出しよりも実行が複雑になります。各共有オブジェクトには、プログラムリンケージテーブル (PLT) が 1 つあり、ここにはそのオブジェクトが参照し、そのオブジェクトの外部にあるすべての関数 (外部関数) のエントリが含まれます。最初は、PLT 内の各外部関数のアドレスは、実際には動的リンカーである ld.so 内のアドレスです。外部関数が初めて呼び出されると、制御が動的リンカーに移り、動的リンカーは、その外部関数への呼び出しを解決し、以降の呼び出しのために、PLT のアドレスにパッチを適用します。
3 つの PLT 命令のいずれか 1 つを実行しているときにプロファイリングイベントが発生した場合、PLT PC は削除され、排他的時間はその呼び出し命令に対応することになります。PLT エントリによる最初の呼び出し時にプロファイリングイベントが発生し、かつリーフ PC が PLT 命令のいずれかではない場合、PLT および ld.so のコードから発生する PC はすべて、包括的時間を集計する擬似的な関数 @plt に帰属します。各共有オブジェクトには、こういった擬似的な関数が 1 つ用意されています。LD_AUDIT インタフェースを使用しているプログラムの場合、PLT エントリが絶対にパッチされない可能性があるとともに、@plt の非リーフ PC の発生頻度が高くなることが考えられます。
シグナルがプロセスに送信されると、各種のレジスタ操作とスタック操作が発生し、シグナル送信時にリーフ PC がシステム関数 sigacthandler() の呼び出しの復帰アドレスを示していたかのように見えます。sigacthandler() は、関数が別の関数を呼び出すのと同じようにして、ユーザー指定のシグナルハンドラを呼び出します。
パフォーマンスアナライザは、シグナル送信で発生したフレームを通常のフレームとして処理します。シグナル送信時のユーザーコードがシステム関数 sigacthandler() の呼び出し元として表示され、sigacthandler() はユーザーのシグナルハンドラの呼び出し元として表示されます。sigacthandler() とあらゆるユーザーシグナルハンドラ、さらにはそれらが呼び出すほかの関数の包括的メトリックスは、割り込まれた関数の包括的メトリックスとして表示されます
コレクタは sigaction() に割り込むことによって、時間データ収集時にはそのハンドラが SIGPROF シグナルのプライマリハンドラになり、ハードウェアカウンタオーバーフローのデータ収集時には SIGEMT シグナルのプライマリハンドラになるようにします。
トラップは命令またはハードウェアによって発行され、トラップハンドラによって捕捉されます。システムトラップは、命令から発行され、カーネルにトラップされるトラップです。すべてのシステムコールは、トラップ命令を使用して実装されます。ハードウェアトラップの例としては、命令 (UltraSPARC III プラットフォームでのレジスタ内容値の fitos 命令など) を最後まで実行できないとき、あるいは命令がハードウェアに実装されていないときに、浮動小数点演算装置から発行されるトラップがあります。
トラップが発行されると、Solaris LWP または Linux カーネルはシステムモードになります。Solaris OS 上では、通常、これでマイクロステートはユーザー CPU 状態からトラップ状態、そしてシステム状態に切り替わります。マイクロステートの切り替わりポイントによっては、トラップの処理に費やされた時間が、システム CPU 時間とユーザー CPU 時間を合計したものとして現れることがあります。この時間は、ユーザーコード内でそのトラップを発行した命令、またはシステムコールに割り当てられます。
一部のシステムコールでは、こうした呼び出しをできるかぎり効率良く処理することが重要とみなされます。こうした呼び出しによって生成されたトラップを高速トラップと呼びます。高速トラップを生成するシステム関数には、gethrtime および gethrvtime があります。これらの関数ではオーバーヘッドを伴うため、マイクロステートは切り替わりません。
その他にも、トラップをできるかぎり効率良く処理することが重要とみなされる環境があります。たとえば、TLB (Translation look aside buffer、変換索引バッファー) ミスやレジスタウィンドウのスピル/フィルの場合も、マイクロステートは切り替わりません。
どちらの場合で、費やされた時間はユーザー CPU 時間として記録されます。ただし、システムモードに CPU モードが切り替えられたため、ハードウェアカウンタは動作していません。このため、これらのトラップの処理に費やされた時間は、なるべく同じ実験で記録された、ユーザー CPU 時間とサイクル時間の差を取ることで求めることができます。
トラップハンドラがユーザーモードに戻るケースもあります。Fortran で 4 バイトメモリー境界に整列された整数に対し、8 バイトのメモリー参照を行うようなトラップです。スタックにトラップハンドラのフレームが現れ、整数ロードまたはストア命令が原因でパフォーマンスアナライザにハンドラの呼び出しが表示される場合があります。
命令がカーネルにトラップされると、そのトラップ命令のあとの命令の実行に長い時間がかかっているようにみえます。これは、カーネルがトラップ命令の実行を完了するまで、その命令の実行を開始できないためです。
特定の関数がその最後で別の関数を呼び出す場合、コンパイラは特別な最適化を行うことができます。新しいフレームを生成するのではなく、呼び出し先が呼び出し元のフレームを再利用し、呼び出し先用の復帰アドレスが呼び出し元からコピーされます。この最適化の目的は、スタックのサイズ削減、および SPARC プラットフォーム上でのレジスタウィンドウの使用削減にあります。
プログラムのソースの呼び出しシーケンスが、次のようになっていると仮定します。
A -> B -> C -> D
A -> B -> C -> D および C に対して末尾呼び出しの最適化を行うと、呼び出しスタックは、関数 A が関数 B、C、D を直接呼び出しているかのようになります。
A -> B A -> C A -> D
つまり、呼び出しツリーがフラットになります。-g オプションを指定してコードをコンパイルした場合、末尾呼び出しの最適化は、4 以上のレベルでのみ行われます。-g オプションなしでコードをコンパイルした場合、2 以上のレベルで末尾呼び出しの最適化が行われます。
Solaris OS では、簡単なプログラムは、単一の LWP (LightWeight Process、軽量プロセス) 上のシングルスレッド内で動作します。マルチスレッド化した実行可能ファイルは、スレッド作成関数を呼び出し、その関数に実行するターゲット関数が渡されます。ターゲットの終了時にスレッドが破棄されます。
Solaris OS では、Solaris スレッドと POSIX スレッド (Pthread) の 2 種類のスレッド実装がサポートされています。Solaris 10 OS 以降、両方のスレッド実装が libc.so に含まれます。
Solaris のスレッドでは、新しく作成されたスレッドは、スレッド作成呼び出しで渡された関数を呼び出す _thread_start() という関数で実行を開始します。このスレッドによって実行されるターゲットが関係するどの呼び出しスタックでも、スタックの先頭は _thread_start() であり、スレッド作成関数の呼び出し元に接続することはありません。このため、作成されたスレッドに関連付けられた包括的メトリックスは、_thread_start() と <Total> 関数にのみ及びます。スレッドの作成に加えて、Solaris のスレッド実装では、スレッドを実行するために LWP が Solaris 上に作成されます。LWPSolaris スレッドによる作成スレッドはそれぞれ特定の LWP に結合されます。
Solaris 10 OS と Linux OS では、明示的なマルチスレッド化に Pthread を使用できます。
どちらの環境でも、新しいスレッドを作成するには、アプリケーションが Pthread API 関数 pthread_create() を呼び出して、関数引数の 1 つとして、ポインタをアプリケーション定義の起動ルーチンに渡します。
Solaris OS では、新しい pthread の実行の開始時に _lwp_start() 関数が呼び出されます。Solaris 10 OS では、_lwp_start() から中間関数 _thr_setup() が呼び出され、その中間関数から pthread_create() で指定されたアプリケーション定義の起動ルーチンが呼び出されます。
Linux OS では、新しい pthread の実行の開始時に Linux 固有のシステム関数 clone() が呼び出され、この関数から別の内部初期化関数 pthread_start_thread() が呼び出され、この関数から、pthread_create() で定義されたアプリケーション定義の起動ルーチンが呼び出されます。コレクタで使用できる Linux メトリックス収集関数はスレッドに固有です。したがって、collect ユーティリティーを実行すると、これは pthread_start_thread() とアプリケーション定義のスレッド起動ルーチンの間に、collector_root() という名前のメトリックス収集関数を割り込ませます。
典型的な開発者にとっては、Java テクロノジーベースのアプリケーションはほかのプログラムと同じように動作します。このアプリケーションは、一般に class.main というメインエントリポイントから始まり、C または C++ アプリケーションの場合と同様に、ほかのメソッドを呼び出すことがあります。
オペレーティングシステムにとっては、Java プログラミング言語で書かれたアプリケーション (純粋なものか、C/C++ が混合しているもの) は JVM ソフトウェアをインスタンス化するプロセスとして動作します。JVM ソフトウェアは C++ ソースからコンパイルされ、_start から実行を開始し、それが main を呼び出すというように処理が進行します。このソフトウェアは .class ファイルまたは .jar ファイルからバイトコードを読み取り、そのプログラムで指定された操作を実行します。指定できる操作の中には、ネイティブ共有オブジェクトの動的な読み込みや、そのオブジェクト内に含まれている各種関数やメソッドへの呼び出しがあります。
JVM ソフトウェアは、従来の言語で記述されたアプリケーションでは一般に行われない多数の動作を行います。起動時に、このソフトウェアはデータ空間に動的に生成されたコードの多数の領域を作成します。これらの領域の 1 つは、アプリケーションのバイトコードメソッドを処理するために使用される実際のインタプリタコードです。
Java テクノロジーベースのアプリケーションの実行中、大半のメソッドは JVM ソフトウェアで解析されます。本書では、これらのメソッドをインタプリタされたメソッドと呼んでいます。Java HotSpot 仮想マシンによって、バイトコードの解析時にパフォーマンスが監視され、頻繁に実行されているメソッドが検出されます。繰り返し実行されているメソッドは、Java HotSpot 仮想マシンによってコンパイルされ、マシンコードが生成される場合があります。マシンコードが生成されたメソッドは、コンパイルされたメソッドと呼びます。仮想マシンでは、その後、メソッドの元のバイトコードを解析せずに、より効率的な、コンパイルされたメソッドが実行されます。コンパイルされたメソッドはアプリケーションのデータ空間に読み込まれ、その後のある時点で読み込み解除することができます。さらに、インタプリタされたコードとコンパイルされたコードの間の変換を行うために、ほかのコードがデータ空間で生成されます。
Java プログラミング言語で書かれたコードは、コンパイルされたネイティブコード、すなわち、C、C++、または Fortran 内へ直接呼び出すこともでき、そのような呼び出しのターゲットをネイティブメソッドと呼びます。
Java プログラミング言語で記述されたアプリケーションは本質的にマルチスレッド型で、ユーザーのプログラム内でスレッドごとに 1 つの JVM ソフトウェアスレッドがあります。Java アプリケーションはまた、シグナル処理、メモリー管理、Java HotSpot 仮想マシンのコンパイルに使用されるハウスキーピングスレッドもいくつかあります。
データの収集は、J2SE の JVMTI にあるさまざまなメソッドを使用して実装されます。
パフォーマンスツールは、各 Solaris LWP または Linux スレッドの存続期間中にイベントを記録するほか、イベント時に呼び出しスタックを記録することによってデータを収集します。任意のアプリケーションの実行の任意の時点で、呼び出しスタックは、プログラムが実行のどの段階まで、またどのように達したかを表します。混合モデル Java アプリケーションが従来の C、C++、および Fortran アプリケーションと異なる 1 つの重要な点は、ターゲットの実行中は常に、意味のある呼び出しスタックとして、Java 呼び出しスタックとマシン呼び出しスタックがあるという点です。両方の呼び出しスタックがプロファイル時に記録され、解析時に調整されます。
Java プログラムに対する時間ベースのプロファイルおよびハードウェアカウンタオーバーフローのプロファイルは、Java 呼び出しスタックとマシン呼び出しスタックの両方が収集されることを除けば、C や C++、Fortran プログラムに対するのと完全に同じ働きをします。
Java プログラミング言語で書かれたアプリケーションについては、パフォーマンスデータを表示するための表現方法として、Java ユーザー表現、Java 上級表現、マシン表現があります。デフォルトでは、データが Java ユーザー表現をサポートする場合は、Java ユーザー表現で表示されます。以降では、これらの 3 つの表現の主な違いをまとめます。
ユーザー表現は、コンパイルされた Java メソッドとインタプリタされた Java メソッドを名前で表示し、ネイティブメソッドをそれらの自然な形式で表示します。実行中は、実行される特定の Java メソッドのインスタンスが多数存在する可能性があります。つまり、インタプリタバージョンと、場合によっては 1 つ以上のコンパイルバージョンが存在する可能性があります。Java ユーザー表現では、すべてのメソッドが 1 つのメソッドとして集計された状態で表示されます。アナライザでは、この表現がデフォルトで選択されます。
Java メソッド (Java ユーザー表現内) の PC は、メソッド ID とそのメソッドへのバイトコードのインデックスに対応し、ネイティブ関数の PC はマシン PC に対応します。Java スレッドの呼び出しスタックには、Java PC とマシン PC が混在している場合があります。この呼び出しスタックには、Java ユーザー表現を持たない Java ハウスキーピングコードに対応するフレームはありません。状況によっては、JVM ソフトウェアは Java スタックを展開することができず、<no Java callstack recorded> という特別な関数を持つシングルフレームが返されます。これは通常、合計時間の 5 ~ 10% にしかなりません。
Java ユーザー表現の関数リストは、Java メソッドと呼び出された任意のネイティブメソッドに対するメトリックスを示します。呼び出し元 - 呼び出し先のパネルには、呼び出しの関係が Java ユーザー表現で示されます。
Java メソッドのソースは、コンパイル元の .java ファイル内のソースコードに対応し、各ソース行にメトリックスがあります。Java メソッドの逆アセンブリは作成されたバイトコードのほか、各バイトコードに対するメトリックスとインタリーブされた Java ソース (入手可能な場合) を示します。
Java ユーザー表現のタイムラインは、Java スレッドのみを示します。各スレッドの呼び出しスタックが、その Java メソッドとともに示されます。
Java ユーザー表現のデータ空間プロファイリングは、現在サポートされていません。
Java 上級表現は、JVM 内部要素の詳細のいくつかを除いては Java ユーザー表現に似ています。Java ユーザー表現では表示されない JVM 内部要素の詳細のいくつかが、Java 上級表現に表されます。Java 上級表現では、タイムラインがすべてのスレッドを示し、ハウスキーピングスレッドの呼び出しスタックはネイティブ呼び出しスタックです。
マシン表現には、JVM ソフトウェアでインタプリタされるアプリケーションからの関数でなく、JVM ソフトウェア自体からの関数が表示されます。また、コンパイルされたメソッドとネイティブメソッドがすべて表示されます。マシン表現は、従来の言語で書かれたアプリケーションの表現と同じように見えます。呼び出しスタックは、JVM フレーム、ネイティブフレーム、およびコンパイル済みメソッドフレームを表示します。JVM フレームの中には、インタプリタされた Java、コンパイルされた Java、およびネイティブコードの間の変移コードを表すものがあります。
コンパイルされたメソッドからのソースは Java ソースに対照して表示され、データは選択されたコンパイル済みメソッドの特定のインスタンスを表します。コンパイルされたメソッドの逆アセンブリは、Java バイトコードでなく作成されたマシンアセンブラコードを示します。呼び出し元 - 呼び出し先の関係はすべてのオーバーヘッドフレームと、インタプリタされたメソッド、コンパイルされたメソッド、ネイティブメソッドの間の遷移を表すすべてのフレームを示します。
マシン表現のタイムラインはすべてのスレッド、LWP または CPU のバーを示し、それぞれの呼び出しスタックはマシン表現呼び出しスタックになります。
OpenMP アプリケーションの実際の実行モデルについては、OpenMP の仕様 (たとえば、『OpenMP Application Program Interface, Version 3.0』の 1.3 節を参照) で説明されています。しかし、仕様には、ユーザーにとって重要と思われるいくつかの実装の詳細が説明されていません。また、Oracle での実際の実装では、直接記録されたプロファイリング情報からユーザーがスレッド間の相互作用を簡単に理解できないことがわかっています。
ほかのシングルスレッドプログラムが実行されるとき同様に、呼び出しスタックが現在位置と、どのようにしてそこまで到達したかのトレースを、ルーチン内の _start と呼ばれる冒頭の命令を始めとして表示します。このルーチンは main を呼び出し、その後、main によって処理が進められ、プログラム内のさまざまなサブルーチンが呼び出されます。サブルーチンにループが含まれている場合、プログラムは、ループ終了条件が満たされるまでループ内のコードを繰り返し実行します。その後、実行は次のコードシーケンスへ進み、以後同様に処理が続きます。
プログラムが OpenMP (または、自動並列化処理) によって並列化されると、動作は異なります。並列化されたプログラムの直感的なモデルでは、メインスレッド (マスタースレッド) が、シングルスレッドプログラムとまったく同じように実行されます。並列ループまたは並列領域に到達すると、追加のスレーブスレッドが出現します。それらの各スレッドはマスタースレッドのクローンであり、それらのスレッドすべてが、ループまたは並列領域のコンテンツを互いに異なる作業チャンク用に並列実行します。すべての作業チャンクが完了すると、すべてのスレッドの同期がとられ、スレーブスレッドが消失し、マスタースレッドが処理を続行します。
並列化されたプログラムの実際の動作は、それほど直接的なものではありません。コンパイラが並列領域またはループ用のコード (または、その他の任意の OpenMP 構造) を生成するとき、それらの内部のコードが抽出され、Oracle 実装で mfunction と呼ばれる独立した関数が作成されます。この関数は、アウトライン関数、またはループ本体関数とも呼ばれます。mfunction の名前は、OpenMP 構造タイプ、抽出元となった関数の名前、その構造が置かれているソース行の行番号を符号化したものです。これらの関数の名前は、アナライザの上級モードとマシンモードでは次の形式で表示されます。ここで、大括弧内の名前は関数の実際のシンボルテーブル名です。
bardo_ -- OMP parallel region from line 9 [_$p1C9.bardo_] atomsum_ -- MP doall from line 7 [_$d1A7.atomsum_]
これらの関数には、ほかのソース構造から生成される別の形式もあり、その場合、名前の中の「OMP 並列領域」は、「MP コンストラクト」、「MP doall」、「OMP 領域」のいずれかに置き換えられます。以降の説明では、これらすべてを総称して「並列領域」と言います。
並列ループ内のコードを実行する各スレッドは、mfunction を複数回呼び出すことができ、1 回呼び出すたびにループ内の 1 つの作業チャンクが実行されます。すべての作業チャンクが完了すると、それぞれのスレッドはライブラリ内の同期ルーチンまたは縮小ルーチンを呼び出します。その後、マスタースレッドが続行される一方、各スレーブスレッドはアイドル状態になり、マスタースレッドが次の並列領域に入るまで待機します。すべてのスケジューリングと同期は、OpenMP ランタイムの呼び出しによって処理されます。
並列領域内のコードは、その実行中、作業チャンクを実行しているか、ほかのスレッドとの同期をとっているか、行うべき追加の作業チャンクを取り出している場合があります。また、ほかの関数を呼び出す場合もあり、それによってさらに別の関数が呼び出される可能性もあります。並列領域内で実行されるスレーブスレッド (またはマスタースレッド) は、それ自体が、またはそれが呼び出す関数から、マスタースレッドとして動作し、独自の並列領域に入って入れ子並列を生成する場合があります。
アナライザは、呼び出しスタックの統計的な標本収集に基づいてデータを収集し、すべてのスレッドにまたがってデータを集計し、収集したデータのタイプに基づき、関数、呼び出し元と呼び出し先、ソース行、および命令を対象にパフォーマンスのメトリックスを表示します。アナライザは、OpenMP プログラムのパフォーマンスに関する情報を、ユーザーモード、上級モード、マシンモードという 3 つのモードのいずれかで提示します。
OpenMP プログラムのデータ収集についての詳細は、OpenMP のユーザーコミュニティー Web サイトの An OpenMP Runtime API for Profiling を参照してください。
プロファイルデータのユーザーモードの表示では、プログラムが実際に「OpenMP ソフトウェア実行の概要」で説明されている直観的モデルに従って実行されたかのように情報が提示されます。マシンモードで表示される実際のデータは、ランタイムライブラリ libmtsk.so の実装の詳細を取り込んだもので、これは、モデルに対応していません。上級モードでは、モデルに合うように変更されたデータと、実際のデータが表示されます。
ユーザーモードでは、プロファイルデータの表示はモデルにさらに近くなるよう変更されるため、記録されたデータやマシンモードの表示と次の 3 つの点で異なっています。
擬似関数は、OpenMP ランタイムライブラリの視点から各スレッドの状態を表すように構築されます。
呼び出しスタックは、前述のように、コードの実行方法のモデルに対応するデータを報告するよう操作されます。
時間ベースのプロファイル実験用に、2 つの追加パフォーマンスメトリックスが構築され、それらは、有益な作業の実行に費やされた時間と、OpenMP ランタイム内での待機に費やされた時間に対応します。メトリックスは、OpenMP ワークメトリックスとOpenMP 待ちメトリックスです。
OpenMP 3.0 プログラムでは、3 つ目のメトリックス、OpenMP オーバーヘッドメトリックスが構築されます。
擬似関数は、スレッドが OpenMP ランタイムライブラリ内でいずれかの状態にあったイベントを反映するために構築され、ユーザーモードおよび上級モード呼び出しスタック上に置かれます。
次の疑似関数が定義されています。
<OMP-overhead> |
OpenMP ライブラリ内で実行中 |
<OMP-idle> |
作業を待っているスレーブスレッド |
<OMP-reduction> |
縮約操作を実行中のスレッド |
<OMP-implicit_barrier> |
暗黙のバリアで待機中のスレッド |
<OMP-explicit_barrier> |
明示的なバリアで待機中のスレッド |
<OMP-lock_wait> |
ロックを待っているスレッド |
<OMP-critical_section_wait> |
critical セクションに入るのを待っているスレッド |
<OMP-ordered_section_wait> |
ordered セクションに入る順番を待っているスレッド |
<OMP-atomic_section_wait> |
OpenMP 原子構造で待機中のスレッド |
スレッドが、擬似関数のいずれかに対応する OpenMP ランタイム状態にある場合、擬似関数がスタック上のリーフ関数として追加されます。スレッドの実際のリーフ関数は、OpenMP ランタイムのどこかに存在する場合には、リーフ関数として <OMP-overhead> に置き換えられます。そうでない場合、OpenMP ランタイムに入っているすべての PC は、ユーザーモードスタックから除外されます。
OpenMP 3.0 プログラムでは、<OMP-overhead> 擬似関数は使用されません。この擬似関数は、OpenMP オーバーヘッドメトリックスに置き換えられています。
OpenMP 実験の場合は、OpenMP を使用せずにプログラムがコンパイルされたときに取得されたものと同様の、再構築された呼び出しスタックが表示されます。目標は、実際の処理の詳細すべてを示すことではなく、プログラムを直観的に理解できるようにプロファイルデータを表示することです。OpenMP ランタイムライブラリが特定の操作を実行しているときは、マスタスレッドとスレーブスレッドの呼び出しスタックがまとめられ、呼び出しスタックに <OMP-*> 擬似関数が追加されます。
OpenMP プログラムの時間プロファイルイベントを処理するときは、OpenMP システム内の 2 つの状態でそれぞれ費やされた時間に対応する 2 つのメトリックスが示されます。「OpenMP ワーク」と「OpenMP 待ち」です。
スレッドがユーザーコードから実行されたときは、逐次か並列かを問わず、「OpenMP ワーク」に時間が累積されます。スレッドが何かを待って先に進めないときは、その待機が busy-wait (spin-wait) であるかスリープ状態であるかを問わず、「OpenMP 待ち」に時間が累積されます。これら 2 つのメトリックスの合計は、時間プロファイル内の「合計 LWP 時間」メトリックに一致します。
OpenMP 待ちメトリックスとOpenMP ワークメトリックスは、ユーザーモード、上級モード、およびマシンモードに表示されます。
上級表示モードで OpenMP 実験を確認する場合、OpenMP ランタイムが特定の操作を実行しているときの <OMP-*> 形式の擬似関数が、ユーザー表示モードと同じように表示されます。ただし、上級表示モードでは、並列化されたループ、タスクなどを表す、コンパイラにより生成された mfunction が別個に表示されます。ユーザーモードでは、コンパイラにより生成されたこれらの mfunction はユーザー関数に含められます。
「マシン」モードでは、すべてのスレッドのネイティブな呼び出しスタックと、コンパイラによって生成されたアウトライン関数が表示されます。
OpenMPプロファイルデータ、マシン表現 プログラムの実行のさまざまな局面における実際の呼び出しスタックは、前述の直感的なモデルに示したものとは大きく異なります。マシンモードでは、呼び出しスタックが測定どおりに表示され、変換は行われず、擬似関数も構築されません。ただし、時間プロファイルのメトリックスは依然として示されます。
次に示す各呼び出しスタックでは、libmtsk は OpenMP ランタイムライブラリ内の呼び出しスタックに入っている 1 つ以上のフレームを表しています。どの関数がどの順序で表示されるかの詳細は、バリア用のコードまたは縮小を行うコードの内部的な実装と同様に、OpenMP のリリースによって異なります。
最初の並列領域の前
最初の並列領域の前最初の並列領域に入る前の時点で存在するスレッドは、マスタースレッドただ 1 つだけです。呼び出しスタックはユーザーモードおよび上級モードの場合と同じです。
マスター |
---|
foo |
main |
_start |
並列領域内で実行中
マスター |
スレーブ 1 |
スレーブ 2 |
スレーブ 3 |
---|---|---|---|
foo-OMP... | |||
libmtsk | |||
foo |
foo-OMP... |
foo-OMP... |
foo-OMP... |
main |
libmtsk |
libmtsk |
libmtsk |
_start |
_lwp_start |
_lwp_start |
_lwp_start |
マシンモードでは、スレーブスレッドはマスターが開始された _start 内ではなく、_lwp_start 内で開始されたものとして示されます。一部のバージョンのスレッドライブラリでは、この関数は _thread_start として表示されます。foo-OMP... の呼び出しは、並列領域に対して生成された mfunction を表します。
すべてのスレッドがバリアの位置にある
マスター |
スレーブ 1 |
スレーブ 2 |
スレーブ 3 |
---|---|---|---|
libmtsk | |||
foo-OMP... | |||
foo |
libmtsk |
libmtsk |
libmtsk |
main |
foo-OMP... |
foo-OMP... |
foo-OMP... |
_start |
_lwp_start |
_lwp_start |
_lwp_start |
スレッドが並列領域内で実行されるときと異なり、スレッドがバリアの位置で待機しているときは、foo と並列領域コード foo-OMP... の間に OpenMP ランタイムからのフレームは存在しません。その理由は、実際の実行には OMP 並列領域関数が含まれていませんが、OpenMP ランタイムがレジスタを操作し、スタック展開で直前に実行された並列領域関数からランタイムバリアコードへの呼び出しが示されるようにするからです。そうしないと、どの並列領域がバリア呼び出しに関連しているかをマシンモードで判定する方法がなくなってしまいます。
並列領域から出たあと
マスター |
スレーブ 1 |
スレーブ 2 |
スレーブ 3 |
---|---|---|---|
foo | |||
main |
libmtsk |
libmtsk |
libmtsk |
_start |
_lwp_start |
_lwp_start |
_lwp_start |
スレーブスレッド内では、呼び出しスタック上にユーザーフレームが存在しません。
入れ子の並列領域内にいるとき
マスター |
スレーブ 1 |
スレーブ 2 |
スレーブ 3 |
スレーブ 4 |
---|---|---|---|---|
bar-OMP... | ||||
foo-OMP... |
libmtsk | |||
libmtsk |
bar | |||
foo |
foo-OMP... |
foo-OMP... |
foo-OMP... |
bar-OMP... |
main |
libmtsk |
libmtsk |
libmtsk |
libmtsk |
_start |
_lwp_start |
_lwp_start |
_lwp_start |
_lwp_start |
スタック展開の定義については、「呼び出しスタックとプログラムの実行」を参照してください。
スタック展開は、次のようないくつかの場合に失敗します。
ユーザーコードによってスタックが破壊されている場合。スタックが破壊された原因によっては、プログラムまたはデータ収集コードでコアダンプする場合があります。
ユーザーコードが、関数呼び出しの標準の ABI 規則に従っていない場合。特に、SPARC プラットフォームで、保存命令が実行される前に復帰レジスタ %o7 が変えられる場合。
どんなプラットフォームでも、手書きのアセンブラコードには規則違反がある場合があります。
リーフ PC の関数内の位置が、呼び出し先のフレームがスタックからポップされた後、また関数が復帰する前である場合。
呼び出しスタックに約 250 を超えるフレームが含まれている場合、この呼び出しスタックを完全に展開するだけの領域がコレクタにはありません。この場合、呼び出しスタックの _start から特定の時点までの関数の PC は実験ファイルに記録されません。疑似関数 <Truncated-stack> は、記録された一番上のフレームを集計するため、<Total> から呼び出されたものとして示されます。
x86 プラットフォームで、最適化された関数のフレームをコレクタが展開できなかった場合。
-E または -P コンパイラオプションを使用して中間ファイルを生成すると、アナライザは元のソースファイルではなく、この中間ファイルを注釈付きソースコードとして使用します。-E を使用して生成された #line 指令は、ソース行へのメトリックスの割り当てで問題が発生する原因となることがあります。
関数が生成されるようにコンパイルされたソースファイルへの参照用の行番号を持たない関数からの命令が存在する場合、次の行が注釈付きのソースに現れます。
function_name -- <instructions without line numbers>
行番号は、次の条件下では欠落することがあります。
-g オプションを指定しないでコンパイルした場合。
コンパイルのあとにデバッグ情報がストリップされた場合、またはその情報を含む実行可能ファイルかオブジェクトファイルが移動、削除、変更された場合。
関数が、オリジナルのソースファイルではなく、#include ファイルから生成されたコードを含む場合。
高レベルの最適化で、コードが異なるファイルの関数からインライン化された場合。
ソースファイルに、ほかのファイルを参照する #line 指令がある場合。たとえば、-E オプションを使用してコンパイルし、その結果の .i ファイルをコンパイルすることによって起こります。-P フラグを使用してコンパイルした際にも起こりえます。
行番号情報を読み取るオブジェクトファイルが見つからない場合。
使用したコンパイラが、不完全な行番号テーブルを生成する場合。
アナライザは、呼び出しスタックの内容を処理して PC 値を生成したあとに、それらの PC をプログラム内の共有オブジェクト、関数、ソース行、および逆アセンブリ行 (命令) にマップします。ここでは、これらのマッピングについて説明します。
プログラムを実行すると、そのプログラムの実行可能ファイルからプロセスがインスタンス化されます。プロセスのアドレス空間には、実行可能な命令を表すテキストが存在する領域や、通常は実行されないデータが存在する領域などの多数の領域があります。通常、呼び出しスタックに記録される PC は、プログラムのいずれかのテキストセグメント内のアドレスに対応しています。
プロセスの先頭テキストセクションは、実行可能ファイルそのものから生成されます。先頭以外のテキストセクションは、プロセスの開始時に実行可能ファイルとともに読み込まれたか、プロセスによって動的に読み込まれた、共有オブジェクトに対応しています。呼び出しスタック内の PC は、呼び出しスタックの記録時に読み込まれた実行可能ファイルと共有オブジェクトに基づいて解決されます。実行可能ファイルと共有オブジェクトはよく似ているため、これらをまとめてロードオブジェクトと呼びます。
共有オブジェクトは、プログラムの実行途中で読み込みおよび読み込みの解除が可能なため、実行中のタイミングによって PC が対応する関数が異なることがあります。また、共有オブジェクトが読み込み解除されたあとに別のアドレスに再度読み込まれた場合は、異なる時点で異なる複数の PC が同じ関数に対応することもあります。
実行可能ファイルまたは共有オブジェクトのどちらであっても、ロードオブジェクトには、コンパイラによって生成された命令を含むテキストセクション、データ用のデータセクション、および各種のシンボルテーブルが含まれます。ロードオブジェクトシンボルテーブルシンボルテーブル、ロードオブジェクト すべてのロードオブジェクトには、ELF シンボルテーブルが存在する必要があります。ELF シンボルテーブルには、そのオブジェクト内で大域的に既知の関数すべての名前とアドレスが含まれます。-g オプションを指定してコンパイルしたロードオブジェクトには、追加のシンボル情報が含まれます。この情報は、ELF シンボルテーブルを補足するもので、非大域的な関数に関する情報、関数の派生元のオブジェクトモジュールに関する補足情報、アドレスをソース行に関連付ける行番号情報で構成されます。
「関数」という用語は、ソースコードで記述された高度な操作を表す一連の命令を表します。この用語は、Fortran で使用されるサブルーチンや、C++ および Java プログラミング言語で使用されるメソッドなども表します。サブルーチン関数 サブルーチンFortranサブルーチンメソッド 関数関数はソースコードで明確に記述され、通常、その名前は、一群のアドレスを表すシンボルテーブル内に出現します。
原則として、ロードオブジェクトのテキストセグメント内のアドレスは関数にマップすることができます。関数ロードオブジェクト内のアドレス ロードオブジェクト関数のアドレス呼び出しスタック上のリーフ PC およびほかのすべての PC で、まったく同じマッピング情報が使用されます。関数の多くは、プログラムのソースモデルに直接対応します。以降の節では、そのような対応関係を持たない関数について説明します。
一般に、関数は大域関数として定義されます。このことは、プログラム内のあらゆる部分で関数名が既知であることを意味します。大域関数の名前は、実行可能ファイル内で一意である必要があります。アドレス空間内に同一名の大域関数が複数存在する場合、実行時リンカーはすべての参照をそのうちの 1 つに決定します。その他の関数は実行されず、関数リストにそれらの関数が含まれることはありません。「概要」タブでは、選択した関数を含む共有オブジェクトおよびオブジェクトモジュールを調べることができます。
さまざまな状況で、同じ関数が異なる名前で認識されることがあります。この一般的な例としては、コードの同一部分に対して、いわゆる弱いシンボルと強いシンボルが使用されている場合があります。一般に、強い名前は対応する弱い名前と同じですが、前に下線 (_) が付きます。スレッドライブラリ内の多くの関数には、強い名前、弱い名前、代替内部シンボルに加えて、pthread および Solaris スレッド用に別の名前があります。いずれの場合も、アナライザの関数リストでは、このうちの 1 つの名前だけが使用されます。選択される名前は、アドレスをアルファベット順に並べた最後のシンボルです。ほとんどの場合は、この名前がユーザーの使用する名前に対応しています。「概要」タブでは、選択した関数のすべてのエイリアス (別名) が表示されます。
別名を持つ関数は、コードの同一部分に複数の名前があることを意味しますが、場合によっては、複数のコード部分で同じ名前が使用されることがあります。
モジュール性を実現するために、関数が静的関数として定義されることがあります。これは、その関数名がプログラムの一部 (一般には、コンパイル済みの 1 つのオブジェクトモジュール) でだけ認識されることを意味します。このような場合、プログラムのまったく異なる部分を参照している同じ名前の複数の関数がアナライザに表示されます。「概要」タブでは、こうした関数を区別するために、それら関数のそれぞれにオブジェクトモジュール名が表示されます。また、こうした関数のどの名前が選択されたとしても、その関数のソース、逆アセンブリ、呼び出し元と呼び出し先を表示することができます。
ライブラリ関数の弱い名前を持つラッパー関数または割り込み関数がプログラムで使用され、そのライブラリ関数の呼び出しに優先されることがあります。一部のラッパー関数は、ライブラリ内の元の関数を呼び出し、その場合は、名前の両方のインスタンスが アナライザの関数リストに表示されます。こうした関数は、元の共有オブジェクトやオブジェクトモジュールが異なるため、それらの情報を基に区別することができます。コレクタも一部のライブラリ関数をラップすることがあり、アナライザには、ラッパー関数と実際の関数の両方が表示されることがあります。
ライブラリ内では、ライブラリ内部の関数名がユーザーの使う関数名と衝突しないようにするために、静的関数がよく使用されます。ライブラリをストリップすると、静的関数の名前はシンボルテーブルから削除されます。このような場合、アナライザは、ストリップ済み静的関数を含むライブラリ内のすべてのテキスト領域ごとに擬似的な名前を生成します。この名前は <static>@0x12345 という形式で、@ 記号に続く文字列は、ライブラリ内のテキスト領域のオフセット位置を表します。アナライザは、連続する複数のストリップ済み静的関数と単一のストリップ済み静的関数を区別できないため、複数のストリップ済み静的関数のメトリックスがまとめて表示されることがあります。
ストリップ済み静的関数は、その PC が静的関数の保存命令のあとに表示されるリーフ PC である場合を除いて、正しい呼び出し元から呼び出されたように表示されます。シンボル情報がない場合、アナライザは保存アドレスを認識しません。このため、復帰レジスタを呼び出し元として使用すべきかどうかは判断できません。復帰レジスタは常に無視されます。複数の関数が、1 つの <static>@0x12345 関数にまとめられることがあるため、実際の呼び出し元または呼び出し先が隣接する関数と区別されないことがあります。
Fortran には、コードの一部に複数のエントリポイントを用意し、呼び出し元が関数の途中を呼び出す手段が用意されています。このようなコードをコンパイルしたときに生成されるコードは、メインのエントリポイントの導入部、代替エントリポイントの導入部、関数のコード本体で構成されます。各導入部では、関数があとで復帰するためのスタックが作成され、そのあとで、コード本体に分岐または接続します。
各エントリポイントの導入部のコードは、そのエントリポイント名を持つテキスト領域に常に対応しますが、サブルーチン本体のコードは、エントリポイント名の 1 つだけを受け取ります。受け取る名前は、コンパイラによって異なります。
多くの場合、導入部の時間はわずかで、アナライザに、サブルーチン本体に関連付けられたエントリポイント以外のエントリポイントに対応する関数が表示されることはほとんどありません。通常、代替エントリポイントを持つ Fortran サブルーチンで費やされる時間を表す呼び出しスタックは、導入部ではなくサブルーチンの本体に PC があり、本体に関連付けられた名前だけが呼び出し先として表示されます。同様に、そうしたサブルーチンからのあらゆる呼び出しは、サブルーチン本体に関連付けられている名前から行われたものとみなされます。
コンパイラは、通常以上の最適化が可能な関数への呼び出しを見分けることができます。こういった呼び出しの一例としては、引数の一部が定数である関数への呼び出しが挙げられます。コンパイラは、最適化できる特定の呼び出しを見つけると、クローンと呼ばれるこの関数のコピーを作成して、最適化コードを生成します。クローン関数名は、特定の呼び出しを識別する、符号化された名前です。アナライザはこの名前を復号化し、クローン生成関数のインスタンスそれぞれを別々に関数リストに表示します。クローン生成関数はそれぞれ別の命令セットを持っているので、注釈付き逆アセンブリリストには、クローン生成関数が別々に表示されます。クローン生成関数はそれぞれ別の命令セットを持っているので、注釈付き逆アセンブリリストにはクローン生成関数が別々に表示されます。各クローン生成関数のソースコードは同じであるため、注釈付きソースリストでは関数のすべてのコピーについてデータが集計されます。
インライン関数とは、実際に呼び出す代わりに、呼び出し位置にコンパイラが生成した命令が挿入される関数です。2 通りのインライン化があり、ともにパフォーマンス向上のために行われ、アナライザに影響します。
C++ のインライン関数定義。このようにインライン化する理由は、関数呼び出しが、インライン化した関数よって行われる作業よりも処理時間がかかるためです。呼び出しの設定をするより、単に呼び出し位置に関数のコードを挿入する方が優れています。一般に、アクセス関数は、必要な命令が 1 つだけであることが多いため、インライン化対象として定義されます。-g オプションを使用してコンパイルすると、関数のインライン化は無効になります。一方、-g0 を指定すると有効になり、これが推奨されます。
高レベルの最適化 (4 および 5) で行われる明示的または自動的なインライン化。明示的および自動的なインライン化は、-g オプションが有効なときにも行われます。この種のインライン化を行うのは、関数呼び出しの時間を節約するための場合もあります。しかし、多くの場合は、レジスタの利用や命令の実行スケジューリングを最適化できる命令数を増やすためです。
いずれのインライン化も、メトリックスの表示に同じ影響を及ぼします。ソースコードに記述されていて、インライン化された関数は、関数リストにも、また、そうした関数のインライン化先の関数の呼び出し先としても表示されません。通常ならば、インライン化された関数の呼び出し位置で包括的メトリックスとみなされるメトリックス (呼び出された関数で費やされた時間を表す) が、実際には呼び出し位置 (インライン化された関数の命令を表す) が原因の排他的メトリックスと報告されます。
インライン化によってデータの解釈が難しくなることがあります。このため、パフォーマンス解析のためにプログラムをコンパイルするときには、インライン化を無効にすることを推奨します。
場合によっては、関数がインライン化されている場合も、いわゆるライン外関数が残されます。一部の呼び出し側ではライン外関数が呼び出され、それ以外では命令がインライン化されます。このような場合、関数は関数リストに含まれますが、関連するメトリックスはライン外呼び出しだけを表します。
関数内のループ、または並列化指令のある領域を並列化する場合、コンパイラは、元のソースコードに含まれていない新しい本体関数を作成します。こうした関数については、「OpenMP ソフトウェア実行の概要」で説明しています。
アナライザでは、これらの関数は通常の関数として表示され、コンパイラにより生成される名前に加えて、抽出元の関数に基づいて名前が割り当てられます。これらの関数の排他的メトリックスおよび包括的メトリックスは、本体関数で費やされる時間を表します。また、構造の抽出元の関数は各本体関数の包括的メトリックスを示します。これが達成される方法については、「OpenMP ソフトウェア実行の概要」で説明しています。
並列ループを含む関数をインライン化した場合、そのコンパイラ生成の本体関数名には、元の関数ではなく、インライン化先の関数の名前が反映されます。
コンパイラ生成本体関数の名前は、-g オプションを指定してコンパイルされたモジュールでのみ復号化することができます。
フィードバック最適化コンパイルで、アウトライン関数が作成されることがあります。それらは、通常では実行されないコード、特に、最終的な最適化コンパイル用のフィードバックの生成に使用される「試験実行」の際に実行されないコードを表しています。一般的な例は、ライブラリ関数の戻り値でエラーチェックを実行するコードです。通常、エラー処理コードは実行されません。ページングと命令キャッシュの動作を向上させるため、こういったコードはアドレス空間の別の場所に移動され、新たな別の関数となります。アウトライン関数の名前は、コードの取り出し元関数の名前や特定のソースコードセクションの先頭の行番号を含む、アウトライン化したコードのセクションに関する情報をエンコードします。これらの符号化された名前は、リリースごとに異なります。アナライザは、読みやすい関数名を表示します。
アウトライン関数は実際には呼び出されるのではなく、ジャンプ先になります。動作をユーザーのソースコードモデルにより近づけるため、アナライザは、main 関数からそのアウトライン部分への擬似的な呼び出しを生成します。
アウトライン関数は、適切な包括的および排他的メトリックスを持つ通常の関数として表示されます。また、アウトライン関数のメトリックスは、アウトライン化元の関数の包括的メトリックスとして追加されます。
フィードバックデータを利用した最適化コンパイルの詳細は、『C ユーザーズガイド』の付録 B、『C++ ユーザーズガイド』の付録 A、または『Fortran ユーザーズガイド』の第 3 章で、-xprofile コンパイラオプションの説明を参照してください。
動的にコンパイルされる関数は、プログラムの実行中にコンパイルされリンクされる関数です。コレクタ API 関数を使用して必要な情報をユーザーが提供しないかぎり、コレクタは C や C++ で記述された動的にコンパイルされる関数に関する情報を把握できません。API 関数については、「動的な関数とモジュール」を参照してください。情報を提供しなかった場合、関数は <Unknown> としてパフォーマンス解析ツールに表示されます。
Java プログラムの場合、コレクタは Java HotSpot 仮想マシンによってコンパイルされるメソッドに関する情報を取得するので、API 関数を使用して情報を提供する必要がありません。ほかのメソッドの場合、パフォーマンスツールはメソッドを実行する JVM ソフトウェアの情報を表示します。Java ユーザー表現では、すべてのメソッドがインタプリタされたバージョンとマージされます。マシン表現では、HotSpot でコンパイルされたバージョンが個別に表示され、JVM 関数はインタプリタされたメソッドごとに表示されます。
特定の条件では、PC が既知の関数にマップされないことがあります。このような場合、PC は <Unknown> という名前の特別な関数にマップされます。
PC が <Unknown> にマップされるのは、次のような場合です。
C または C++ で記述された関数が動的に生成され、この関数に関する情報がコレクタ API 関数によってコレクタに提供されない場合。コレクタ API 関数の詳細については、「動的な関数とモジュール」を参照してください。
Java メソッドが動的にコンパイルされるが、Java プロファイリングが無効である場合。
PC が実行可能ファイルまたは共有オブジェクトのデータセクション内のアドレスに対応している場合。たとえば、SPARC V7 版の libc.so のデータセクションには、複数の関数 (.mul、.div など) があります。コードがデータセクションにあるため、SPARC V8 または SPARC V9 プラットフォームで動作していることをライブラリが検出したときに、動的に書き換えてマシン命令を利用できるようになります。
実験ファイルに記録されない実行可能ファイルのアドレス空間内の共有オブジェクトに PC が対応する場合。
PC が既知のロードオブジェクト内に存在しない場合。もっとも考えられる原因は、展開に失敗して、PC 値として記録された値が PC ではなく、別のワードである場合です。PC が復帰レジスタのとき、既知のロードオブジェクト内に存在しないように見える場合は、<Unknown> 関数に割り当てられずに無視されます。
コレクタにシンボリック情報がない JVM ソフトウェアの内部部分に PC がマップしている場合。
<Unknown> 関数の呼び出し元および呼び出し先は、呼び出しスタックの前および次の PC に対応しており、通常どおり処理されます。
擬似関数は、スレッドが OpenMP ランタイムライブラリ内の何らかの状態にあったイベントを反映するために構築され、ユーザーモード呼び出しスタック上に置かれます。次の疑似関数が定義されています。
<OMP-overhead> |
OpenMP ライブラリ内で実行中 |
<OMP-idle> |
作業を待っているスレーブスレッド |
<OMP-reduction> |
縮約操作を実行中のスレッド |
<OMP-implicit_barrier> |
暗黙のバリアで待機中のスレッド |
<OMP-explicit_barrier> |
明示的なバリアで待機中のスレッド |
<OMP-lock_wait> |
ロックを待っているスレッド |
<OMP-critical_section_wait> |
critical セクションに入るのを待っているスレッド |
<OMP-ordered_section_wait> |
ordered セクションに入る順番を待っているスレッド |
ユーザー表現では、<JVM-System> 関数は、JVM ソフトウェアが Java プログラムの実行以外のアクションを行うために使用した時間を示します。JVM ソフトウェアは、ガベージコレクションや HotSpot コンパイルなどのタスクを、この時間間隔内で実行します。<JVM-System> はデフォルトで関数リストに表示されます。
<no Java callstack recorded> 関数は <Unknown> 関数に似ていますが、Java スレッド用に、Java ユーザー表現でのみ使用されます。コレクタが Java スレッドからイベントを受信すると、ネイティブスタックを展開し、JVM ソフトウェアを呼び出して対応する Java スタックを取得します。その呼び出しが何らかの理由で失敗すると、擬似関数 <no Java callstack recorded> でアナライザ内にイベントが表示されます。JVM ソフトウェアが呼び出しスタックの報告を拒否する可能性があるのは、デッドロックを回避するためか、Java スタックを展開すると過剰な同期化が発生するときです。
呼び出しスタックの個々の関数のメトリックスを記録するためにアナライザが使用するバッファーのサイズは制限されています。呼び出しスタックのサイズが大きくなってバッファーが満杯になった場合に、呼び出しスタックがそれ以上大きくなると、アナライザは関数のプロファイル情報を減らすようになります。ほとんどのプログラムでは、排他的 CPU 時間の大部分はリーフ関数に費やされるため、アナライザは、エントリ関数 _start() および main() を始めとするもっとも重要度の低いスタック下部の関数のメトリックスをドロップします。ドロップされた関数のメトリックスは、1 つの擬似関数 <Truncated-stack> にまとめられます。<Truncated-stack> 関数は、Java プログラムでも表示される場合があります。
<Total> 関数は、プログラム全体を表すために使用される擬似的な構成概念です。すべてのパフォーマンスメトリックスは、呼び出しスタック上の関数のメトリックスとして加算されるほかに、<Total> という特別な関数のメトリックスに加算されます。この関数は関数リストの先頭に表示され、そのデータを使用してほかの関数のデータの概略を見ることができます。呼び出し元 - 呼び出し先リストでは、任意のプログラム実行のメインスレッドにおける _start() の名目上の呼び出し元、また作成されたスレッドの _thread_start() の名目上の呼び出し元として表示されます。スタックの展開が不完全であった場合、<Total> 関数は、<Truncated-stack> の呼び出し元として表示される可能性があります。
次の関数は、ハードウェアカウンタオーバーフロープロファイルに関連します。
collector_not_program_related: カウンタはプログラムに関連しません。
collector_hwcs_out_of_range: カウンタは、オーバーフローシグナルを生成せずにオーバーフロー値を超えたように見えます。値が記録され、カウンタがリセットされます。
collector_hwcs_frozen: カウンタは、オーバーフロー値を超えて停止されたように見えますが、オーバーフローシグナルが消失したように見えます。値が記録され、カウンタがリセットされます。
collector_hwc_ABORT: 一般に特権付きプロセスがカウンタの制御権を取得したときに、ハードウェアカウンタの読み取りに失敗し、ハードウェアカウンタの収集が終了しました。
collector_record_counter: ハードウェアカウンタイベントの処理中および記録中に蓄積されたカウントで、ハードウェアカウンタオーバーフロープロファイルのオーバーヘッドの一部を占めます。このカウントが <Total> カウントの大きな割合を占める場合は、オーバーフロー間隔を増やすこと (すなわち、より低い分解能の構成) が推奨されます。
インデックスオブジェクトは、各パケットに記録されたデータからインデックスを計算できる要素のセットを表します。事前定義されているインデックスオブジェクトセットは、スレッド、CPU、標本、秒です。その他のインデックスオブジェクトは、er_print indxobj_define コマンドから直接実行するか、.er.rc ファイル内で定義できます。アナライザでインデックスオブジェクトを定義するには、「表示」メニューから「データ表示方法を設定」を選択し、「タブ」タブを選択し、「カスタムインデックスオブジェクトを追加」ボタンをクリックします。
パケットごとにインデックスが計算され、パケットに関連付けられているメトリックスが、そのインデックスのインデックスオブジェクトに追加されます。インデックス -1 は、<Unknown> インデックスオブジェクトにマップしています。インデックスオブジェクトの階層表示は意味がないので、インデックスオブジェクトのメトリックスはすべて排他的メトリックスです。
メモリー演算に対応するハードウェアカウンタイベントからの PC が、原因と思われるメモリー参照命令に正しくバックトラックするように処理されると、アナライザは、コンパイラからハードウェアプロファイルサポート情報内に提供された命令識別子と記述子を使用して、関連するプログラムデータオブジェクトを生成します。
データオブジェクトという用語は、ソースコードに記述されているプログラムの定数、変数、配列、および構造体や共用体などの集合体のほか、別個の集合体要素を示す場合に使用します。データオブジェクトのタイプとそのサイズはソース言語によって異なります。多くのデータオブジェクトの名前は明示的にソースプログラム内で付けられますが、名前が付けられないものもあります。データオブジェクトの中には、ほかの単純なデータオブジェクトから生成または集計され、より複雑なデータオブジェクトの集合になるものもあります。
各データオブジェクトは、1 つのスコープに関連付けられています。スコープとは、そのオブジェクトが定義され、そのオブジェクトを参照できるソースプログラムの領域のことで、大域 (ロードオブジェクトなど)、特定のコンパイルユニット (オブジェクトファイル)、または関数のいずれかになります。同一のデータオブジェクトを異なるスコープで定義したり、特定のデータオブジェクトを異なるスコープで異なる方法で参照することができます。
バックトラッキングを有効にして収集された、メモリー操作に関するハードウェアカウンタイベントからのデータ派生メトリックスは、関連するプログラムのデータオブジェクトタイプに属するものとされ、そのデータオブジェクトを含む集合体と、<Unknown> や <Scalars> などすべてのデータオブジェクトを含むと見なされる <Total> 擬似データオブジェクトに伝搬します。<Unknown> の各種サブタイプは、<Unknown> の集合体まで伝搬します。次の節では、<Total>、<Scalars>、および <Unknown> の各データオブジェクトについて説明します。
データオブジェクトは、宣言された型と名前の組み合わせで記述します。単純なスカラーデータオブジェクト {int i} は、int 型の変数 i を記述しているのに対して、{const+pointer+int p} は、int 型 p への定数ポインタを記述しています。型名のスペースは「_」(アンダースコア) に置き換えられ、名前の付いていないデータオブジェクトは「-」(ハイフン)、たとえば {double_precision_complex -} という名前で表されます。
集合体全体も同様に、foo_t 型の構造体の場合は {structure:foo_t} と表します。集合体の要素では、その要素のコンテナを追加指定する必要があります。たとえば、前述の foo_t 型の構造体のメンバーである int 型の i の場合は {structure:foo_t}.{int i} となります。集合体はそれ自体、(さらに大きい) 集合体の要素になることも可能で、対応する記述子は集合体記述子を連結したもの、最終的にはスカラー記述子になります。
完全修飾された記述子は、データオブジェクトを明確にするために必ずしも必要ではありませんが、データオブジェクトの識別を支援するために一般的な完全指定を示します。
<Total> データオブジェクトは、プログラムのデータオブジェクト全体を表すために使用される擬似的な構造です。あらゆるパフォーマンスメトリックスは、異なるデータオブジェクト (およびそのオブジェクトが属する集合体) のメトリックスとして加算されるほかに、<Total> という特別なデータオブジェクトに加算されます。このデータオブジェクトはデータオブジェクトリストの先頭に表示され、そのデータを使用してほかのデータオブジェクトのデータの概略を見ることができます。
集合体要素のパフォーマンスメトリックスは、関連する集合体のメトリック値に加算されますが、すべてのスカラー定数および変数のパフォーマンスメトリックスは擬似的な <Scalars> データオブジェクトのメトリック値にさらに加算されます。
さまざまな状況下で、特定のデータオブジェクトにイベントデータをマップできない場合があります。このような場合、データは <Unknown> という特別なデータオブジェクトと、次に説明するいずれかの要素にマップされます。
トリガー PC を持つモジュールが -xhwcprof を使用してコンパイルされていない
トリガー PC を持つモジュールが -xhwcprof を使用してコンパイルされていない イベントの原因となっている命令またはデータオブジェクトは、オブジェクトコードがハードウェアカウンタプロファイルサポートを指定してコンパイルされていなかったので、識別されませんでした。
バックトラッキングで有効な分岐先が検出できなかった
バックトラッキングで有効な分岐先を検出できなかったイベントの原因となっている命令は、コンパイルオブジェクト内で提供されたハードウェアプロファイルサポート情報が、バックトラッキングの妥当性を検証するには不十分だったため、識別されませんでした。
バックトラッキングで分岐先をトラバースした
バックトラッキングで分岐先をトラバースしたイベントの原因となっている命令またはデータオブジェクトは、バックトラッキングで命令ストリーム内から制御転送ターゲットが検出されたため、識別されませんでした。
識別する記述子がコンパイラから提供されなかった
識別する記述子がコンパイラから提供されなかったバックトラッキングで原因と思われるメモリー参照命令を判別しましたが、それに関連するデータオブジェクトはコンパイラで指定されませんでした。
タイプ情報がない
タイプ情報がないバックトラッキングでイベントの原因と思われる命令を判別しましたが、その命令は、コンパイラによってメモリー参照命令として識別されませんでした。
コンパイラが提供したシンボリック情報から判別不能
コンパイラが提供したシンボリック情報から判別不能バックトラッキングで原因と思われるメモリー参照命令を判別しましたが、その命令はコンパイラによって識別されなかったため、それに関連するデータオブジェクトも判別できません。コンパイラのテンポラリは一般に識別されません。
ジャンプ命令または呼び出し命令によってバックトラッキングが阻止された
バックトラッキングが、ジャンプ命令または呼び出し命令によって阻止された イベントの原因となっている命令は、バックトラッキングで命令ストリームの中から分岐命令または呼び出し命令が検出されたため、識別されませんでした。
バックトラッキングでトリガー PC が検出されなかった
バックトラッキングでトリガー PC が検出されなかったイベントの原因である命令を、最大のバックトラッキング範囲内から検出できませんでした。
トリガー命令のあとでレジスタが変更されたため、仮想アドレスを特定できなかった
トリガー命令のあとでレジスタが変更されたため、仮想アドレスを判別できなかったレジスタがハードウェアカウンタのスキッド中に上書きされたため、データオブジェクトの仮想アドレスを判別できませんでした。
メモリー参照命令で有効な仮想アドレスが指定されなかった
メモリー参照命令で有効な仮想アドレスが指定されなかった データオブジェクトの仮想アドレスが有効であるように見えませんでした。
メモリーオブジェクトは、キャッシュ行、ページ、およびメモリーバンクなど、メモリーサブシステム内のコンポーネントです。このオブジェクトは、記録された仮想アドレスや物理アドレスから計算されたインデックスから決定されます。メモリーオブジェクトは、仮想ページおよび物理ページについて 8K バイト、64K バイト、512K バイト、および 4M バイトのサイズで事前定義されています。それ以外は、er_print ユーティリティーで mobj_define コマンドによって定義できます。また、アナライザの「メモリーオブジェクトを追加」ダイアログボックスを使用してカスタムメモリーオブジェクトを定義することもできます。このダイアログボックスを開くには、「データ表示方法の設定」ダイアログボックスで「カスタムオブジェクトを追加」ボタンをクリックします。