ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris Studio 12.3: パフォーマンスアナライザ Oracle Solaris Studio 12.3 Information Library (日本語) |
<no Java callstack recorded> 関数
ハードウェアカウンタオーバーフロープロファイルに関連する関数
呼び出しスタックは、プログラム内の命令を示す一連のプログラムカウンタ (Program Counter、PC) アドレスです。リーフ PC と呼ばれる最初の PC はスタックの一番下に位置し、次に実行される命令のアドレスを表します。次の PC はそのリーフ PC を含む関数の呼び出しアドレス、そして、その次の PC がその関数の呼び出しアドレスというようにして、これがスタックの先頭まで続きます。こうしたアドレスはそれぞれ、復帰アドレスと呼びます。呼び出しスタックの記録プロセスでは、プログラムスタックから復帰アドレスが取得されます。これは、スタックの展開と呼ばれています。展開の失敗については、Incomplete Stack Unwindsを参照してください。「不完全なスタック展開」
呼び出しスタック内のリーフ PC は、この PC が存在する関数にパフォーマンスデータの排他的メトリックを割り当てるときに使用されます。スタック上の各 PC は、リーフ PC も含めて、その PC が存在する関数に包括的メトリックを割り当てるために使用されます。
ほとんどの場合、記録された呼び出しスタック内の PC は、プログラムのソースコードに現れる関数に自然な形で対応しており、パフォーマンスアナライザが報告するメトリックもそれらの関数に直接対応しています。しかし、プログラムの実際の実行は、単純で直観的なプログラム実行モデルと対応しないことがあり、その場合は、アナライザの報告するメトリックが混乱を招くことがあります。こうした事例については、Mapping Addresses to Program Structureを参照してください。「プログラム構造へのアドレスのマッピング」
プログラムの実行でもっとも単純なものは、シングルスレッドプログラムが専用のロードオブジェクト内の関数を呼び出す場合です。
プログラムがメモリーに読み込まれて実行が開始されると、初期実行アドレス、初期レジスタセット、スタック (スクラッチデータの格納および関数の相互の呼び出し方法の記録に使用されるメモリー領域) からなるコンテキストが作成されます。初期アドレスは常に、あらゆる実行可能ファイルに組み込まれる _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 シグナルのプライマリハンドラになるようにします。
トラップは命令またはハードウェアによって発行され、トラップハンドラによって捕捉されます。システムトラップは、命令から発行され、カーネルにトラップされるトラップです。すべてのシステムコールは、トラップ命令を使用して実装されます。ハードウェアトラップの例としては、命令を完了できないとき、あるいは命令がハードウェアに実装されていないときに、浮動小数点ユニットから発行されるものがあります。
トラップが発行されると、カーネルはシステムモードになります。Oracle Solaris 上では、マイクロステートは通常、ユーザー CPU 状態からトラップ状態、そしてシステム状態に切り替わります。マイクロステートの切り替わりポイントによっては、トラップの処理に費やされた時間が、システム CPU 時間とユーザー CPU 時間を合計したものとして現れることがあります。この時間は、ユーザーコード内でそのトラップを発行した命令、またはシステムコールに割り当てられます。
一部のシステムコールでは、こうした呼び出しをできるかぎり効率良く処理することが重要とみなされます。こうした呼び出しによって生成されたトラップを高速トラップと呼びます。高速トラップを生成するシステム関数には、gethrtime および gethrvtime があります。これらの関数ではオーバーヘッドを伴うため、マイクロステートは切り替わりません。
その他にも、トラップをできるかぎり効率良く処理することが重要とみなされる環境があります。たとえば、TLB (Translation look aside buffer、変換索引バッファー) ミスやレジスタウィンドウのスピル/フィルの場合も、マイクロステートは切り替わりません。
TLB (translation lookaside buffer) ミス いずれの場合も、費やされた時間はユーザー CPU 時間として記録されます。ただし、システムモードに CPU モードが切り替えられたため、ハードウェアカウンタは動作していません。このため、これらのトラップの処理に費やされた時間は、なるべく同じ実験で記録された、ユーザー CPU 時間とサイクル時間の差を取ることで求めることができます。
トラップハンドラがユーザーモードに戻るケースもあります。Fortran で 4 バイトメモリー境界に整列された整数に対し、8 バイトのメモリー参照を行うようなトラップです。スタックにトラップハンドラのフレームが現れ、整数ロードまたはストア命令が原因でパフォーマンスアナライザにハンドラの呼び出しが表示される場合があります。
命令がカーネルにトラップされると、そのトラップ命令のあとの命令の実行に長い時間がかかっているようにみえます。これは、カーネルがトラップ命令の実行を完了するまで、その命令の実行を開始できないためです。
特定の関数がその最後で別の関数を呼び出す場合、コンパイラは特別な最適化を行うことができます。新しいフレームを生成するのではなく、呼び出し先が呼び出し元のフレームを再利用し、呼び出し先用の復帰アドレスが呼び出し元からコピーされます。この最適化の目的は、スタックのサイズ削減、および SPARC プラットフォーム上でのレジスタウィンドウの使用削減にあります。
プログラムのソースの呼び出しシーケンスが、次のようになっていると仮定します。
A -> B -> C -> D
A -> B -> C -> DB および C に対して末尾呼び出しの最適化を行うと、呼び出しスタックは、関数 A が関数 B、C、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 が Solaris 上に作成されます。LWPSolaris スレッドによる作成スレッドはそれぞれ特定の LWP に結合されます。
Oracle Solaris と Linux では、明示的なマルチスレッド化に Pthread を使用できます。
どちらの環境でも、新しいスレッドを作成するには、アプリケーションが Pthread API 関数 pthread_create を呼び出して、関数引数の 1 つとして、ポインタをアプリケーション定義の起動ルーチンに渡します。()
Oracle Solaris 10 より前の 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 テクロノジーベースのアプリケーションはほかのプログラムと同じように動作します。このアプリケーションは、一般に 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 アプリケーションが従来の C、C++、および Fortran アプリケーションと異なる 1 つの重要な点は、ターゲットの実行中は常に、意味のある呼び出しスタックとして、Java 呼び出しスタックとマシン呼び出しスタックがあるという点です。両方の呼び出しスタックがプロファイル時に記録され、解析時に調整されます。
Java プログラムに対する時間ベースのプロファイルおよびハードウェアカウンタオーバーフローのプロファイルは、Java 呼び出しスタックとマシン呼び出しスタックの両方が収集されることを除けば、C や C++、Fortran プログラムに対するのと完全に同じ働きをします。
パフォーマンスアナライザには、Java プログラミング言語で書かれたアプリケーションのパフォーマンスデータを表示するための表示モードとして、ユーザーモード、上級モード、マシンモードの 3 つが用意されています。デフォルトでは、データがユーザーモードをサポートする場合は、ユーザーモードで表示されます。以降では、これらの 3 つの表示モードの主な違いをまとめます。
ユーザーモードは、コンパイルされた Java メソッドとインタプリタされた Java メソッドを名前で表示し、ネイティブメソッドをそれらの自然な形式で表示します。実行中は、実行される特定の Java メソッドのインスタンスが多数存在する可能性があります。つまり、インタプリタバージョンと、場合によっては 1 つ以上のコンパイルバージョンが存在する可能性があります。ユーザーモードでは、すべてのメソッドが 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 ユーザー表現のデータ領域プロファイリングは、現在サポートされていません。
上級モードはユーザーモードに似ていますが、ユーザーモードでは表示されない JVM 内部要素の詳細のいくつかが、上級モードでは表示されます。上級モードでは、タイムラインがすべてのスレッドを示し、ハウスキーピングスレッドの呼び出しスタックはネイティブ呼び出しスタックです。
マシンモードでは、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_]
bardo_ -- 行 9 からの OMP 並列領域 [_$p1C9.bardo_] atomsum_ -- 行 7 からの MP doall [_$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 ランタイムライブラリ内でいずれかの状態にあったイベントを反映するために構築され、ユーザーモードおよび上級モード呼び出しスタック上に置かれます。
次の疑似関数が定義されています。
|
スレッドが、擬似関数のいずれかに対応する 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 つのメトリックの合計は、時間プロファイル内の総スレッド数メトリックに一致します。
OpenMP 待ちメトリックと OpenMP ワークメトリックは、ユーザーモード、上級モード、およびマシンモードに表示されます。
上級表示モードで OpenMP 実験を確認する場合、OpenMP ランタイムが特定の操作を実行しているときの <OMP-*> 形式の擬似関数が、ユーザー表示モードと同じように表示されます。ただし、上級表示モードでは、並列化されたループ、タスクなどを表す、コンパイラにより生成された mfunction が別個に表示されます。ユーザーモードでは、コンパイラにより生成されたこれらの mfunction はユーザー関数に含められます。
「マシン」モードでは、すべてのスレッドのネイティブな呼び出しスタックと、コンパイラによって生成されたアウトライン関数が表示されます。
OpenMPプロファイルデータ、マシン表現 プログラムの実行のさまざまな局面における実際の呼び出しスタックは、前述の直感的なモデルに示したものとは大きく異なります。マシンモードでは、呼び出しスタックが測定どおりに表示され、変換は行われず、擬似関数も構築されません。ただし、時間プロファイルのメトリックは依然として示されます。
次に示す各呼び出しスタックでは、libmtsk は OpenMP ランタイムライブラリ内の呼び出しスタックに入っている 1 つ以上のフレームを表しています。どの関数がどの順序で表示されるかの詳細は、バリア用のコードまたは縮小を行うコードの内部的な実装と同様に、OpenMP のリリースによって異なります。
最初の並列領域の前
最初の並列領域の前最初の並列領域に入る前の時点で存在するスレッドは、マスタースレッドただ 1 つだけです。呼び出しスタックはユーザーモードおよび上級モードの場合と同じです。
|
並列領域内で実行中
|
マシンモードでは、スレーブスレッドはマスターが開始された _start 内ではなく、_lwp_start 内で開始されたものとして示されます。一部のバージョンのスレッドライブラリでは、この関数は _thread_start として表示されます。foo-OMP... の呼び出しは、並列領域に対して生成された mfunction を表します。
すべてのスレッドがバリアの位置にある
|
スレッドが並列領域内で実行されるときと異なり、スレッドがバリアの位置で待機しているときは、foo と並列領域コード foo-OMP... の間に OpenMP ランタイムからのフレームは存在しません。その理由は、実際の実行には OMP 並列領域関数が含まれていませんが、OpenMP ランタイムがレジスタを操作し、スタック展開で直前に実行された並列領域関数からランタイムバリアコードへの呼び出しが示されるようにするからです。そうしないと、どの並列領域がバリア呼び出しに関連しているかをマシンモードで判定する方法がなくなってしまいます。
並列領域から出たあと
|
スレーブスレッド内では、呼び出しスタック上にユーザーフレームが存在しません。
入れ子の並列領域内にいるとき
|
スタック展開の定義については、「呼び出しスタックとプログラムの実行」を参照してください。
スタック展開は、次のようないくつかの場合に失敗します。
ユーザーコードによってスタックが破壊されている場合。スタックが破壊された原因によっては、プログラムまたはデータ収集コードでコアダンプする場合があります。
ユーザーコードが、関数呼び出しの標準の 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 フラグを使用してコンパイルした際にも起こりえます。
行番号情報を読み取るオブジェクトファイルが見つからない場合。
使用したコンパイラが、不完全な行番号テーブルを生成する場合。