Oracle Solaris Studio 12.2: パフォーマンスアナライザ

プログラム構造へのアドレスのマッピング

アナライザは、呼び出しスタックの内容を処理して PC 値を生成したあとに、それらの PC をプログラム内の共有オブジェクト、関数、ソース行、および逆アセンブリ行 (命令) にマップします。ここでは、これらのマッピングについて説明します。

プロセスイメージ

プログラムを実行すると、そのプログラムの実行可能ファイルからプロセスがインスタンス化されます。プロセスのアドレス空間には、実行可能な命令を表すテキストが存在する領域や、通常は実行されないデータが存在する領域などの多数の領域があります。通常、呼び出しスタックに記録される PC は、プログラムのいずれかのテキストセグメント内のアドレスに対応しています。

プロセスの先頭テキストセクションは、実行可能ファイルそのものから生成されます。先頭以外のテキストセクションは、プロセスの開始時に実行可能ファイルとともに読み込まれたか、プロセスによって動的に読み込まれた、共有オブジェクトに対応しています。呼び出しスタック内の PC は、呼び出しスタックの記録時に読み込まれた実行可能ファイルと共有オブジェクトに基づいて解決されます。実行可能ファイルと共有オブジェクトはよく似ているため、これらをまとめてロードオブジェクトと呼びます。

共有オブジェクトは、プログラムの実行途中で読み込みおよび読み込みの解除が可能なため、実行中のタイミングによって PC が対応する関数が異なることがあります。また、共有オブジェクトが読み込み解除されたあとに別のアドレスに再度読み込まれた場合は、異なる時点で異なる複数の PC が同じ関数に対応することもあります。

ロードオブジェクトと関数

実行可能ファイルまたは共有オブジェクトのどちらであっても、ロードオブジェクトには、コンパイラによって生成された命令を含むテキストセクション、データ用のデータセクション、および各種のシンボルテーブルが含まれます。ロードオブジェクトシンボルテーブルシンボルテーブル、ロードオブジェクト すべてのロードオブジェクトには、ELF シンボルテーブルが存在する必要があります。ELF シンボルテーブルには、そのオブジェクト内で大域的に既知の関数すべての名前とアドレスが含まれます。-g オプションを指定してコンパイルしたロードオブジェクトには、追加のシンボル情報が含まれます。この情報は、ELF シンボルテーブルを補足するもので、非大域的な関数に関する情報、関数の派生元のオブジェクトモジュールに関する補足情報、アドレスをソース行に関連付ける行番号情報で構成されます。

関数」という用語は、ソースコードで記述された高度な操作を表す一連の命令を表します。この用語は、Fortran で使用されるサブルーチンや、C++ および Java プログラミング言語で使用されるメソッドなども表します。サブルーチン関数 サブルーチンFortranサブルーチンメソッド 関数関数はソースコードで明確に記述され、通常、その名前は、一群のアドレスを表すシンボルテーブル内に出現します。

原則として、ロードオブジェクトのテキストセグメント内のアドレスは関数にマップすることができます。関数ロードオブジェクト内のアドレス ロードオブジェクト関数のアドレス呼び出しスタック上のリーフ PC およびほかのすべての PC で、まったく同じマッピング情報が使用されます。関数の多くは、プログラムのソースモデルに直接対応します。以降の節では、そのような対応関係を持たない関数について説明します。

別名を持つ関数

一般に、関数は大域関数として定義されます。このことは、プログラム内のあらゆる部分で関数名が既知であることを意味します。大域関数の名前は、実行可能ファイル内で一意である必要があります。アドレス空間内に同一名の大域関数が複数存在する場合、実行時リンカーはすべての参照をそのうちの 1 つに決定します。その他の関数は実行されず、関数リストにそれらの関数が含まれることはありません。「概要」タブでは、選択した関数を含む共有オブジェクトおよびオブジェクトモジュールを調べることができます。

さまざまな状況で、同じ関数が異なる名前で認識されることがあります。この一般的な例としては、コードの同一部分に対して、いわゆる弱いシンボルと強いシンボルが使用されている場合があります。一般に、強い名前は対応する弱い名前と同じですが、前に下線 (_) が付きます。スレッドライブラリ内の多くの関数には、強い名前、弱い名前、代替内部シンボルに加えて、pthread および Solaris スレッド用に別の名前があります。いずれの場合も、アナライザの関数リストでは、このうちの 1 つの名前だけが使用されます。選択される名前は、アドレスをアルファベット順に並べた最後のシンボルです。ほとんどの場合は、この名前がユーザーの使用する名前に対応しています。「概要」タブでは、選択した関数のすべてのエイリアス (別名) が表示されます。

一意でない関数名

別名を持つ関数は、コードの同一部分に複数の名前があることを意味しますが、場合によっては、複数のコード部分で同じ名前が使用されることがあります。

ストリップ済み共有ライブラリの静的関数

ライブラリ内では、ライブラリ内部の関数名がユーザーの使う関数名と衝突しないようにするために、静的関数がよく使用されます。ライブラリをストリップすると、静的関数の名前はシンボルテーブルから削除されます。このような場合、アナライザは、ストリップ済み静的関数を含むライブラリ内のすべてのテキスト領域ごとに擬似的な名前を生成します。この名前は <static>@0x12345 という形式で、@ 記号に続く文字列は、ライブラリ内のテキスト領域のオフセット位置を表します。アナライザは、連続する複数のストリップ済み静的関数と単一のストリップ済み静的関数を区別できないため、複数のストリップ済み静的関数のメトリックスがまとめて表示されることがあります。

ストリップ済み静的関数は、その PC が静的関数の保存命令のあとに表示されるリーフ PC である場合を除いて、正しい呼び出し元から呼び出されたように表示されます。シンボル情報がない場合、アナライザは保存アドレスを認識しません。このため、復帰レジスタを呼び出し元として使用すべきかどうかは判断できません。復帰レジスタは常に無視されます。複数の関数が、1 つの <static>@0x12345 関数にまとめられることがあるため、実際の呼び出し元または呼び出し先が隣接する関数と区別されないことがあります。

Fortran での代替エントリポイント

Fortran には、コードの一部に複数のエントリポイントを用意し、呼び出し元が関数の途中を呼び出す手段が用意されています。このようなコードをコンパイルしたときに生成されるコードは、メインのエントリポイントの導入部、代替エントリポイントの導入部、関数のコード本体で構成されます。各導入部では、関数があとで復帰するためのスタックが作成され、そのあとで、コード本体に分岐または接続します。

各エントリポイントの導入部のコードは、そのエントリポイント名を持つテキスト領域に常に対応しますが、サブルーチン本体のコードは、エントリポイント名の 1 つだけを受け取ります。受け取る名前は、コンパイラによって異なります。

多くの場合、導入部の時間はわずかで、アナライザに、サブルーチン本体に関連付けられたエントリポイント以外のエントリポイントに対応する関数が表示されることはほとんどありません。通常、代替エントリポイントを持つ Fortran サブルーチンで費やされる時間を表す呼び出しスタックは、導入部ではなくサブルーチンの本体に PC があり、本体に関連付けられた名前だけが呼び出し先として表示されます。同様に、そうしたサブルーチンからのあらゆる呼び出しは、サブルーチン本体に関連付けられている名前から行われたものとみなされます。

クローン生成関数

コンパイラは、通常以上の最適化が可能な関数への呼び出しを見分けることができます。こういった呼び出しの一例としては、引数の一部が定数である関数への呼び出しが挙げられます。コンパイラは、最適化できる特定の呼び出しを見つけると、クローンと呼ばれるこの関数のコピーを作成して、最適化コードを生成します。クローン関数名は、特定の呼び出しを識別する、符号化された名前です。アナライザはこの名前を復号化し、クローン生成関数のインスタンスそれぞれを別々に関数リストに表示します。クローン生成関数はそれぞれ別の命令セットを持っているので、注釈付き逆アセンブリリストには、クローン生成関数が別々に表示されます。クローン生成関数はそれぞれ別の命令セットを持っているので、注釈付き逆アセンブリリストにはクローン生成関数が別々に表示されます。各クローン生成関数のソースコードは同じであるため、注釈付きソースリストでは関数のすべてのコピーについてデータが集計されます。

インライン関数

インライン関数とは、実際に呼び出す代わりに、呼び出し位置にコンパイラが生成した命令が挿入される関数です。2 通りのインライン化があり、ともにパフォーマンス向上のために行われ、アナライザに影響します。

いずれのインライン化も、メトリックスの表示に同じ影響を及ぼします。ソースコードに記述されていて、インライン化された関数は、関数リストにも、また、そうした関数のインライン化先の関数の呼び出し先としても表示されません。通常ならば、インライン化された関数の呼び出し位置で包括的メトリックスとみなされるメトリックス (呼び出された関数で費やされた時間を表す) が、実際には呼び出し位置 (インライン化された関数の命令を表す) が原因の排他的メトリックスと報告されます。


注 –

インライン化によってデータの解釈が難しくなることがあります。このため、パフォーマンス解析のためにプログラムをコンパイルするときには、インライン化を無効にすることを推奨します。


場合によっては、関数がインライン化されている場合も、いわゆるライン外関数が残されます。一部の呼び出し側ではライン外関数が呼び出され、それ以外では命令がインライン化されます。このような場合、関数は関数リストに含まれますが、関連するメトリックスはライン外呼び出しだけを表します。

コンパイラ生成の本体関数

関数内のループ、または並列化指令のある領域を並列化する場合、コンパイラは、元のソースコードに含まれていない新しい本体関数を作成します。こうした関数については、「OpenMP ソフトウェア実行の概要」で説明しています。

アナライザでは、これらの関数は通常の関数として表示され、コンパイラにより生成される名前に加えて、抽出元の関数に基づいて名前が割り当てられます。これらの関数の排他的メトリックスおよび包括的メトリックスは、本体関数で費やされる時間を表します。また、構造の抽出元の関数は各本体関数の包括的メトリックスを示します。これが達成される方法については、「OpenMP ソフトウェア実行の概要」で説明しています。

並列ループを含む関数をインライン化した場合、そのコンパイラ生成の本体関数名には、元の関数ではなく、インライン化先の関数の名前が反映されます。


注 –

コンパイラ生成本体関数の名前は、-g オプションを指定してコンパイルされたモジュールでのみ復号化することができます。


アウトライン関数

フィードバック最適化コンパイルで、アウトライン関数が作成されることがあります。それらは、通常では実行されないコード、特に、最終的な最適化コンパイル用のフィードバックの生成に使用される「試験実行」の際に実行されないコードを表しています。一般的な例は、ライブラリ関数の戻り値でエラーチェックを実行するコードです。通常、エラー処理コードは実行されません。ページングと命令キャッシュの動作を向上させるため、こういったコードはアドレス空間の別の場所に移動され、新たな別の関数となります。アウトライン関数の名前は、コードの取り出し元関数の名前や特定のソースコードセクションの先頭の行番号を含む、アウトライン化したコードのセクションに関する情報をエンコードします。これらの符号化された名前は、リリースごとに異なります。アナライザは、読みやすい関数名を表示します。

アウトライン関数は実際には呼び出されるのではなく、ジャンプ先になります。動作をユーザーのソースコードモデルにより近づけるため、アナライザは、main 関数からそのアウトライン部分への擬似的な呼び出しを生成します。

アウトライン関数は、適切な包括的および排他的メトリックスを持つ通常の関数として表示されます。また、アウトライン関数のメトリックスは、アウトライン化元の関数の包括的メトリックスとして追加されます。

フィードバックデータを利用した最適化コンパイルの詳細は、『C ユーザーズガイド』の付録 B、『C++ ユーザーズガイド』の付録 A、または『Fortran ユーザーズガイド』の第 3 章で、-xprofile コンパイラオプションの説明を参照してください。

動的にコンパイルされる関数

動的にコンパイルされる関数は、プログラムの実行中にコンパイルされリンクされる関数です。コレクタ API 関数を使用して必要な情報をユーザーが提供しないかぎり、コレクタは C や C++ で記述された動的にコンパイルされる関数に関する情報を把握できません。API 関数については、「動的な関数とモジュール」を参照してください。情報を提供しなかった場合、関数は <Unknown> としてパフォーマンス解析ツールに表示されます。

Java プログラムの場合、コレクタは Java HotSpot 仮想マシンによってコンパイルされるメソッドに関する情報を取得するので、API 関数を使用して情報を提供する必要がありません。ほかのメソッドの場合、パフォーマンスツールはメソッドを実行する JVM ソフトウェアの情報を表示します。Java ユーザー表現では、すべてのメソッドがインタプリタされたバージョンとマージされます。マシン表現では、HotSpot でコンパイルされたバージョンが個別に表示され、JVM 関数はインタプリタされたメソッドごとに表示されます。

<Unknown> 関数

特定の条件では、PC が既知の関数にマップされないことがあります。このような場合、PC は <Unknown> という名前の特別な関数にマップされます。

PC が <Unknown> にマップされるのは、次のような場合です。

<Unknown> 関数の呼び出し元および呼び出し先は、呼び出しスタックの前および次の PC に対応しており、通常どおり処理されます。

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 セクションに入る順番を待っているスレッド 

<JVM-System> 関数

ユーザー表現では、<JVM-System> 関数は、JVM ソフトウェアが Java プログラムの実行以外のアクションを行うために使用した時間を示します。JVM ソフトウェアは、ガベージコレクションや HotSpot コンパイルなどのタスクを、この時間間隔内で実行します。<JVM-System> はデフォルトで関数リストに表示されます。

<no Java callstack recorded> 関数

<no Java callstack recorded> 関数は <Unknown> 関数に似ていますが、Java スレッド用に、Java ユーザー表現でのみ使用されます。コレクタが Java スレッドからイベントを受信すると、ネイティブスタックを展開し、JVM ソフトウェアを呼び出して対応する Java スタックを取得します。その呼び出しが何らかの理由で失敗すると、擬似関数 <no Java callstack recorded> でアナライザ内にイベントが表示されます。JVM ソフトウェアが呼び出しスタックの報告を拒否する可能性があるのは、デッドロックを回避するためか、Java スタックを展開すると過剰な同期化が発生するときです。

<Truncated-stack> 関数

呼び出しスタックの個々の関数のメトリックスを記録するためにアナライザが使用するバッファーのサイズは制限されています。呼び出しスタックのサイズが大きくなってバッファーが満杯になった場合に、呼び出しスタックがそれ以上大きくなると、アナライザは関数のプロファイル情報を減らすようになります。ほとんどのプログラムでは、排他的 CPU 時間の大部分はリーフ関数に費やされるため、アナライザは、エントリ関数 _start() および main() を始めとするもっとも重要度の低いスタック下部の関数のメトリックスをドロップします。ドロップされた関数のメトリックスは、1 つの擬似関数 <Truncated-stack> にまとめられます。<Truncated-stack> 関数は、Java プログラムでも表示される場合があります。

<Total> 関数

<Total> 関数は、プログラム全体を表すために使用される擬似的な構成概念です。すべてのパフォーマンスメトリックスは、呼び出しスタック上の関数のメトリックスとして加算されるほかに、<Total> という特別な関数のメトリックスに加算されます。この関数は関数リストの先頭に表示され、そのデータを使用してほかの関数のデータの概略を見ることができます。呼び出し元 - 呼び出し先リストでは、任意のプログラム実行のメインスレッドにおける _start() の名目上の呼び出し元、また作成されたスレッドの _thread_start() の名目上の呼び出し元として表示されます。スタックの展開が不完全であった場合、<Total> 関数は、<Truncated-stack> の呼び出し元として表示される可能性があります。

ハードウェアカウンタオーバーフロープロファイルに関連する関数

次の関数は、ハードウェアカウンタオーバーフロープロファイルに関連します。