Oracle® Developer Studio 12.5: パフォーマンスアナライザ

印刷ビューの終了

更新: 2016 年 6 月
 
 

呼び出しスタックとプログラムの実行

呼び出しスタックは、プログラム内の命令を示す一連のプログラムカウンタ (PC) アドレスです。リーフ PC と呼ばれる最初の PC はスタックのいちばん下に位置し、次に実行される命令のアドレスを表します。次の PC は、リーフ PC を含む関数の呼び出しのアドレスです。次の PC はその関数の呼び出しのアドレスであり、このような関係がスタックの先頭に達するまで続きます。こうしたアドレスはそれぞれ、復帰アドレスと呼びます。呼び出しスタックの記録プロセスでは、プログラムスタックから復帰アドレスが取得されます。これは、スタックの展開と呼ばれています。展開の失敗については、不完全なスタック展開を参照してください。

呼び出しスタック内のリーフ PC は、この PC が存在する関数にパフォーマンスデータの排他的メトリックを割り当てるときに使用されます。スタック上の各 PC は、リーフ PC も含めて、その PC が存在する関数に包括的メトリックを割り当てるために使用されます。

ほとんどの場合、記録された呼び出しスタック内の PC は、プログラムのソースコードに現れる関数に自然な形で対応しており、パフォーマンスアナライザが報告するメトリックもそれらの関数に直接対応しています。しかし、プログラムの実際の実行は、単純で直観的なプログラム実行モデルと対応しないことがあり、その場合は、パフォーマンスアナライザの報告するメトリックが混乱を招くことがあります。こうした事例については、プログラム構造へのアドレスのマッピングを参照してください。

シングルスレッド実行と関数呼び出し

プログラムの実行でもっとも単純なものは、シングルスレッドプログラムが専用のロードオブジェクト内の関数を呼び出す場合です。

プログラムがメモリーに読み込まれて実行が開始されると、初期実行アドレス、初期レジスタセット、スタック (スクラッチデータの格納および関数の相互の呼び出し方法の記録に使用されるメモリー領域) からなるコンテキストが作成されます。初期アドレスは常に、あらゆる実行可能ファイルに組み込まれる _start() 関数の先頭位置になります。

プログラムを実行すると、たとえば関数呼び出しや条件文を表すことがある分岐命令があるまで、命令が順実行されます。分岐点では、分岐先が示すアドレスに制御が渡されて、そこから実行が続行されます。(SPARC では通常、分岐の次の命令は実行するようすでにコミットされており、この命令は分岐遅延スロット命令と呼ばれます。ただし、分岐命令には、この分岐遅延スロット命令の実行を無効にするものもあります。

呼び出しを表す命令シーケンスが実行されると、復帰アドレスがレジスタに書き込まれ、呼び出された関数の最初の命令から実行が続行されます。

ほとんどの場合は、この呼び出し先の関数に含まれる最初の数個の命令のどこかで、新しいフレーム (関数に関する情報を格納するためのメモリー領域) がスタックにプッシュされ、そのフレームに復帰アドレスが格納されます。復帰アドレスに使用されるレジスタは、呼び出された関数がほかの関数を呼び出すときに使用できます。関数から制御が戻されようとすると、スタックからフレームがポップされ、関数の呼び出し元のアドレスに制御が戻されます。

共有オブジェクト間の関数の呼び出し

共有オブジェクト内の関数が別の共有オブジェクトの関数を呼び出す場合は、同じプログラム内の単純な関数の呼び出しよりも実行が複雑になります。各共有オブジェクトには、プログラムリンケージテーブル (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 シグナルのプライマリハンドラになるようにします。

トラップ

トラップは命令またはハードウェアによって発行され、トラップハンドラによって捕捉されます。システムトラップは、命令から発行され、カーネルにトラップされるトラップです。すべてのシステムコールは、トラップ命令を使用して実装されます。ハードウェアトラップの例としては、命令を完了できないとき、あるいは命令がハードウェアに実装されていないときに、浮動小数点ユニットから発行されるものがあります。

トラップが発行されると、カーネルはシステムモードになります。Oracle Solaris 上では、マイクロステートは通常、ユーザー CPU 状態からトラップ状態、そしてシステム状態に切り替わります。マイクロステートの切り替わりポイントによっては、トラップの処理に費やされた時間が、システム CPU 時間とユーザー CPU 時間を合計したものとして現れることがあります。この時間は、ユーザーコード内でそのトラップを発行した命令、またはシステムコールに割り当てられます。

一部のシステムコールでは、呼び出しをできるだけ効率的に処理することが重要であるとみなされます。こうした呼び出しによって生成されたトラップを高速トラップと呼びます。高速トラップを生成するシステム関数には、gethrtime および gethrvtime があります。これらの関数ではオーバーヘッドを伴うため、マイクロステートは切り替わりません。

このほかにも、トラップをできるだけ効率的に処理することが重要であるとみなされる場合があります。 たとえば、TLB (トランスレーションルックアサイドバッファー) ミスやレジスタウィンドウのスピル/フィルの場合も、マイクロステートは切り替わりません。

TLB (translation lookaside buffer) ミス いずれの場合も、費やされた時間はユーザー CPU 時間として記録されます。ただし、システムモードに CPU モードが切り替えられたため、ハードウェアカウンタは動作していません。このため、これらのトラップの処理に費やされた時間は、なるべく同じ実験で記録された、ユーザー CPU 時間とサイクル時間の差を取ることで求めることができます。

トラップハンドラがユーザーモードに戻るケースもあります。Fortran で 4 バイトの境界に整列された整数に対し、8 バイト整数のメモリー参照を行うようなトラップです。 そのトラップハンドラのフレームがスタックに現れるため、整数のロードまたはストア命令に起因するそのハンドラの呼び出しがパフォーマンスアナライザに表示される場合があります。

命令がカーネルにトラップされると、そのトラップ命令のあとの命令の実行に長い時間がかかっているようにみえます。これは、カーネルがトラップ命令の実行を完了するまで、その命令の実行を開始できないためです。

末尾呼び出しの最適化

特定の関数で最後に実行することが別の関数の呼び出しである場合、コンパイラは常に、ある特定の最適化を実行できます。 新しいフレームを生成するのではなく、呼び出し先が呼び出し元のフレームを再利用し、呼び出し先のための復帰アドレスが呼び出し元からコピーされます。この最適化の目的は、スタックのサイズ削減、および SPARC プラットフォーム上でのレジスタウィンドウの使用削減にあります。

プログラムのソースの呼び出しシーケンスが、次のようになっていると仮定します。

A -> B -> C -> D

BC の末尾呼び出しが最適化されている場合、呼び出しスタックは、関数 A が関数 BC、および D を直接呼び出しているかのように見えます。

A -> B
A -> C
A -> D

呼び出しツリーが平坦化されます。-g オプションを指定してコードをコンパイルした場合、末尾呼び出しの最適化は、4 以上のレベルでのみ行われます。-g オプションなしでコードをコンパイルした場合、2 以上のレベルで末尾呼び出しの最適化が行われます。

明示的なマルチスレッド化

単純なプログラムは、シングルスレッド内で実行されます。マルチスレッド化された実行可能ファイルはスレッド作成関数を呼び出し、その関数に実行されるターゲット関数が渡されます。ターゲットの終了時にスレッドが破棄されます。

Oracle Solaris では、Solaris スレッドと POSIX スレッド (Pthread) の 2 種類のスレッド実装がサポートされています。Oracle Solaris 10 以降、両方のスレッド実装が libc.so に含まれます。

Solaris スレッドでは、新しく作成されたスレッドは、スレッド作成呼び出しで渡された関数を呼び出す _thread_start() と呼ばれる関数で実行を開始します。このスレッドによって実行されるターゲットが関係するどの呼び出しスタックでも、スタックの先頭は _thread_start() であり、スレッド作成関数の呼び出し元に接続することはありません。そのため、作成されたスレッドに関連付けられた包括的メトリックは、_thread_start() および <Total> 関数までしか伝播されません。スレッドの作成に加えて、Solaris のスレッド実装では、スレッドを実行するために LWP が Oracle Solaris 上に作成されます。LWPSolaris スレッドによる作成スレッドはそれぞれ特定の LWP に結合されます。

Oracle Solaris と Linux では、明示的なマルチスレッド化に Pthread を使用できます。

どちらの環境でも、新しいスレッドを作成するには、アプリケーションが Pthread API 関数 pthread_create() を呼び出して、関数引数の 1 つとして、ポインタをアプリケーション定義の起動ルーチンに渡します。

Oracle Solaris 10 より前の Oracle Solaris バージョンでは、新しい pthread の実行の開始時に _lwp_start() 関数が呼び出されます。Oracle Solaris 10 以降では、_lwp_start() から中間関数 _thrp_setup() が呼び出され、そこから pthread_create() で指定されたアプリケーション定義の起動ルーチンが呼び出されます。

Linux オペレーティングシステムでは、新しい pthread の実行の開始時に Linux 固有のシステム関数 clone() が呼び出され、そこから別の内部初期化関数 pthread_start_thread() が呼び出され、そこから、pthread_create() で指定されたアプリケーション定義の起動ルーチンが呼び出されます。コレクタで使用できる Linux メトリック収集関数はスレッドに固有です。そのため、collect ユーティリティーが実行されると、このユーティリティーは、pthread_start_thread() とアプリケーション定義のスレッド起動ルーチンの間に collector_root() という名前のメトリック収集関数を割り込ませます。

Java テクノロジーベースのソフトウェア実行の概要

典型的な開発者にとっては、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 にあるさまざまなメソッドを使用して実装されます。

Java 呼び出しスタックとマシン呼び出しスタック

パフォーマンスツールは、各スレッドの存続期間中にイベントを記録するほか、イベント時に呼び出しスタックを記録することによってデータを収集します。任意のアプリケーションの実行の任意の時点で、呼び出しスタックは、プログラムが実行のどの段階まで、またどのように達したかを表します。混合モデルの Java アプリケーションが従来の C、C++、および Fortran アプリケーションと異なる 1 つの重要な点は、ターゲットの実行中のどの時点でも、Java 呼び出しスタックとマシン呼び出しスタックという 2 つの呼び出しスタックが意味を持つことです。どちらの呼び出しスタックもプロファイリング中に記録され、解析中に調整されます。

クロックプロファイリングとハードウェアカウンタオーバーフロープロファイリング

Java プログラムに対するクロックプロファイリングとハードウェアカウンタオーバーフロープロファイリングは、Java 呼び出しスタックとマシン呼び出しスタックの両方が収集される点を除き、C、C++、および Fortran プログラムの場合と同様に機能します。

Java プロファイリングの表示モード

パフォーマンスアナライザには、Java プログラミング言語で記述されたアプリケーションのパフォーマンスデータを表示するための表示モードとして、ユーザーモード、上級モード、マシンモードの 3 つが用意されています。データがユーザーモードをサポートしている場合、デフォルトではユーザーモードが示されます。以降では、これらの 3 つの表示モードの主な違いをまとめます。

Java プロファイリングデータのユーザー表示モード

ユーザーモードでは、コンパイルされた Java メソッドや解釈された Java メソッドが名前で表示され、ネイティブメソッドがその自然な形式で表示されます。実行中は、解釈されたバージョンと 1 つまたは複数のコンパイルされたバージョンなど、特定の Java メソッドに対して多数のインスタンスが実行される可能性があります。ユーザーモードでは、すべてのメソッドが 1 つのメソッドとして集約されて表示されます。この表示モードは、パフォーマンスアナライザではデフォルトで選択されます。

ユーザー表示モードにある 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 プロファイリングデータの上級表示モード

上級モードはユーザーモードに似ていますが、ユーザーモードでは表示されない JVM 内部要素の詳細のいくつかが、上級モードでは表示されます。上級モードでは、「タイムライン」にはすべてのスレッドが表示されます。ハウスキーピングスレッドの呼び出しスタックは、ネイティブ呼び出しスタックです。

Java プロファイリングデータのマシン表示モード

マシンモードでは、JVM ソフトウェアでインタプリタされるアプリケーションからの関数ではなく、JVM ソフトウェア自体からの関数が表示されます。また、コンパイルされたメソッドとネイティブメソッドがすべて表示されます。マシンモードは、従来の言語で書かれたアプリケーションのものと同じように見えます。呼び出しスタックは、JVM フレーム、ネイティブフレーム、およびコンパイル済みメソッドフレームを表示します。JVM フレームの中には、インタプリタされた Java、コンパイルされた Java、およびネイティブコードの間の変移コードを表すものがあります。

コンパイルされたメソッドのソースは、Java ソースに対して示されます。このデータは、選択されているコンパイルされたメソッドの特定の例を表します。コンパイルされたメソッドの逆アセンブリは、Java バイトコードではなく、生成されたマシンアセンブラコードを示します。呼び出し元 - 呼び出し先の関係はすべてのオーバーヘッドフレームと、インタプリタされたメソッド、コンパイルされたメソッド、ネイティブメソッドの間の遷移を表すすべてのフレームを示します。

マシン表示モードのタイムラインはすべてのスレッド、LWP または CPU のバーを示し、それぞれの呼び出しスタックはマシンモード呼び出しスタックになります。

OpenMP実行の概要

OpenMP アプリケーションの実際の実行モデルについては、OpenMP の仕様 (たとえば、『OpenMP Application Program Interface, Version 3.0』の 1.3 セクションを参照) で説明されています。ただし、この仕様では、ユーザーにとって重要ないくつかの実装の詳細が規定されていません。Oracle の実際の実装は、直接記録されたプロファイリング情報を使用しても、スレッドがどのように相互作用するかをユーザーが容易に理解できないようになっています。

シングルスレッドプログラムが実行される場合と同様に、その呼び出しスタックが現在の位置と、どのようにしてそこまで到達したかを示すトレースを示します。このトレースは _start と呼ばれるルーチン内の開始命令から始まり、そのルーチンが main を呼び出し、それがさらに処理を進めてプログラム内のさまざまなサブルーチンを呼び出します。サブルーチンにループが含まれている場合、プログラムは、ループ終了条件が満たされるまでループ内のコードを繰り返し実行します。その後、実行は次のコードシーケンスへ進み、以後同様に処理が続きます。

プログラムが OpenMP で (自動並列化により) 並列化されている場合、動作は異なります。並列化されたプログラムの直感的なモデルでは、メインスレッド (マスタースレッド) が、シングルスレッドプログラムとまったく同じように実行されます。そのスレッドが並列ループまたは並列領域に到達すると、追加のスレーブスレッドが現れます。各スレッドはマスタースレッドのクローンであり、それらのすべてのスレッドが、異なる作業チャンクごとにループまたは並列領域の内容を並列に実行します。すべての作業チャンクが完了すると、すべてのスレッドの同期がとられ、スレーブスレッドが消失し、マスタースレッドが処理を続行します。

並列化されたプログラムの実際の動作は、それほど直接的なものではありません。コンパイラが並列領域またはループ用のコード (または、その他の任意の OpenMP 構造) を生成するとき、それらの内部のコードが抽出され、Oracle 実装で mfunction と呼ばれる独立した関数が作成されます。この関数は、アウトライン関数、またはループ本体関数とも呼ばれます。mfunction の名前は、OpenMP 構造タイプ、抽出元となった関数の名前、その構造が置かれているソース行の行番号を符号化したものです。これらの関数の名前は、パフォーマンスアナライザの上級モードとマシンモードで次の形式で表示されます。ここで、括弧内の名前はその関数の実際のシンボルテーブル名です。

bardo_ -- OMP parallel region from line 9 [_$p1C9.bardo_]
atomsum_ -- MP doall from line 7 [_$d1A7.atomsum_]

bardo_ -- 行 9 からの OMP 並列領域 [_$p1C9.bardo_] atomsum_ -- 行 7 からの MP doall [_$d1A7.atomsum_]これらの関数には、ほかのソース構造から生成される別の形式もあり、その場合、名前の中の OMP 並列領域は、MP コンストラクトMP doallOMP 領域のいずれかに置き換えられます。以降の説明では、これらすべてを総称して「並列領域」と言います。

並列ループ内のコードを実行する各スレッドは、mfunction を複数回呼び出すことができ、1 回呼び出すたびにループ内の 1 つの作業チャンクが実行されます。すべての作業チャンクが完了すると、それぞれのスレッドはライブラリ内の同期ルーチンまたは縮小ルーチンを呼び出します。その後、マスタースレッドが続行される一方、各スレーブスレッドはアイドル状態になり、マスタースレッドが次の並列領域に入るまで待機します。すべてのスケジューリングと同期は、OpenMP ランタイムの呼び出しによって処理されます。

並列領域内のコードは、その実行中、作業チャンクを実行しているか、ほかのスレッドとの同期をとっているか、行うべき追加の作業チャンクを取り出している場合があります。また、ほかの関数を呼び出す場合もあり、それによってさらに別の関数が呼び出される可能性もあります。並列領域内で実行されるスレーブスレッド (またはマスタースレッド) は、それ自体が、またはそれが呼び出す関数から、マスタースレッドとして動作し、独自の並列領域に入って入れ子並列を生成する場合があります。

パフォーマンスアナライザは、呼び出しスタックの統計的な標本収集に基づいてデータを収集し、すべてのスレッドにわたってデータを集計したあと、収集したデータの種類に基づいてパフォーマンスのメトリックを関数、呼び出し元と呼び出し先、ソース行、および命令に対して表示します。パフォーマンスアナライザは、OpenMP プログラムのパフォーマンスに関する情報を、ユーザーモード、上級モード、マシンモードの 3 つの表示モードのいずれかで表示します。

OpenMP プログラムのデータ収集についての詳細は、OpenMP のユーザーコミュニティー Web サイトの An OpenMP Runtime API for Profiling を参照してください。

OpenMP プロファイルデータのユーザー表示モード

プロファイルデータのユーザーモードの表示では、プログラムが実際にOpenMP実行の概要 で説明されている直観的モデルに従って実行されたかのように情報が提示されます。マシンモードで表示される実際のデータは、ランタイムライブラリ libmtsk.so の実装の詳細を取り込んだもので、これは、モデルに対応していません。上級モードでは、モデルに合うように変更されたデータと、実際のデータが表示されます。

ユーザーモードでは、プロファイルデータの表示はモデルにさらに近くなるよう変更されるため、記録されたデータやマシンモードの表示とは次の点で異なっています。

  • 擬似関数は、OpenMP ランタイムライブラリの視点から各スレッドの状態を表すように構築されます。

  • 呼び出しスタックは、前述のように、コードの実行方法のモデルに対応するデータを報告するよう操作されます。

  • クロックプロファイリング実験の場合、有効な作業の実行に費やされた時間と、OpenMP ランタイムでの待機に費やされた時間に対応する 2 つの追加のパフォーマンスメトリックが構築されます。メトリックは、OpenMP ワークメトリックとOpenMP 待ちメトリックです。

  • OpenMP 3.0 および 4.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_wait>
OpenMP 原子構造で待機中のスレッド

スレッドが、擬似関数のいずれかに対応する OpenMP ランタイム状態にある場合、擬似関数がスタック上のリーフ関数として追加されます。スレッドの実際のリーフ関数が OpenMP ランタイム内のどこかに存在する場合、その関数は、リーフ関数として <OMP-overhead> に置き換えられます。そうでない場合、OpenMP ランタイムに入っているすべての PC は、ユーザーモードスタックから除外されます。

OpenMP 3.0 および 4.0 プログラムでは、<OMP-overhead> 擬似関数は使用されません。この擬似関数は、OpenMP オーバーヘッドメトリックに置き換えられています。

ユーザーモード呼び出しスタック

OpenMP 実験の場合は、OpenMP を使用せずにプログラムがコンパイルされたときに取得されたものと同様の、再構築された呼び出しスタックが表示されます。目標は、実際の処理の詳細すべてを示すことではなく、プログラムを直観的に理解できるようにプロファイルデータを表示することです。マスタースレッドとスレーブスレッドの呼び出しスタックが調整され、OpenMP ランタイムライブラリが特定の操作を実行しているときは擬似関数 <OMP-*> が呼び出しスタックに追加されます。

OpenMP メトリック

OpenMP プログラムの時間プロファイルイベントを処理するときは、OpenMP システム内の 2 つの状態でそれぞれ費やされた時間に対応する 2 つのメトリックが示されます。「OpenMP ワーク」と「OpenMP 待ち」です。

スレッドがユーザーコードから実行されたときは、逐次か並列かを問わず、「OpenMP ワーク」に時間が累積されます。スレッドが何かを待って先に進めないときは、その待機が busy-wait (spin-wait) であるかスリープ状態であるかを問わず、「OpenMP 待ち」に時間が累積されます。これら 2 つのメトリックの合計は、時間プロファイル内の総スレッド数メトリックに一致します。

OpenMP 待ちメトリックと OpenMP ワークメトリックは、ユーザーモード、上級モード、およびマシンモードに表示されます。

OpenMP プロファイリングデータの上級表示モード

上級表示モードで OpenMP 実験を確認する場合、OpenMP ランタイムが特定の操作を実行しているときは、ユーザー表示モードと同様に <OMP-*> という形式の擬似関数が表示されます。ただし、上級表示モードでは、並列化されたループ、タスクなどを表す、コンパイラにより生成された mfunction が別個に表示されます。ユーザーモードでは、コンパイラにより生成されたこれらの mfunction はユーザー関数に含められます。

OpenMP プロファイリングデータのマシン表示モード

「マシン」モードでは、すべてのスレッドのネイティブな呼び出しスタックと、コンパイラによって生成されたアウトライン関数が表示されます。

実行のさまざまなフェーズでのプログラムの実際の呼び出しスタックは、ユーザーモデルで説明したものとはまったく異なります。マシンモードでは、呼び出しスタックが測定どおりに表示され、変換は行われず、擬似関数も構築されません。ただし、時間プロファイルのメトリックは依然として示されます。

次に示す各呼び出しスタックでは、libmtsk は OpenMP ランタイムライブラリ内の呼び出しスタックに入っている 1 つ以上のフレームを表しています。どの関数がどの順序で表示されるかの詳細は、バリア用のコードまたは縮小を行うコードの内部的な実装と同様に、OpenMP のリリースによって異なります。

  1. 最初の並列領域の前

    最初の並列領域の前最初の並列領域に入る前の時点で存在するスレッドは、マスタースレッドただ 1 つだけです。呼び出しスタックはユーザーモードおよび上級モードの場合と同じです。

    マスター
    foo
    main
    _start
  2. 並列領域内で実行中

    マスター
    スレーブ 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 を表します。

  3. すべてのスレッドがバリアの位置にある

    マスター
    スレーブ 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 ランタイムがレジスタを操作し、スタック展開で直前に実行された並列領域関数からランタイムバリアコードへの呼び出しが示されるようにするからです。そうしないと、どの並列領域がバリア呼び出しに関連しているかをマシンモードで判定する方法がなくなってしまいます。

  4. 並列領域から出たあと

    マスター
    スレーブ 1
    スレーブ 2
    スレーブ 3
    foo
    main
    libmtsk
    libmtsk
    libmtsk
    _start
    _lwp_start
    _lwp_start
    _lwp_start

    スレーブスレッド内では、呼び出しスタック上にユーザーフレームが存在しません。

  5. 入れ子の並列領域内にいるとき

    マスター
    スレーブ 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 フラグでコンパイルした場合にも発生する可能性があります。

  • 行番号情報を読み取るオブジェクトファイルが見つからない場合。

  • 使用したコンパイラが、不完全な行番号テーブルを生成する場合。