プログラムのパフォーマンス解析 ホーム目次前ページへ次ページへ索引


第 6 章

上級項目: 標本アナライザとデータ

標本アナライザは、標本コレクタの収集したデータを読み込み、パフォーマンス測定結果に変換します。測定結果 (メトリック) は、目的とするプログラムのさまざまな要素に対して計算されます。収集されるイベントは、2 つに構成されます。

この章では、以下の項目を説明します。

イベント固有データとその内容

記録されるイベント固有のデータには、正確な時刻、スレッド ID、LWP ID が含まれます。時間データを使って、実行の一部を選択することができます。スレッドおよび LWP の ID を使って、スレッドおよび LWP のサブセットを選択することができます。また、各イベントは固有の raw データを生成します。raw データについては、以下で説明します。

時間ベースのプロファイル

時間ベースのプロファイルは、各 LWP に対して送られたプロファイル信号ごとに集計したチック数 (時間カウント) のセットで構成されます。カーネルの管理するマイクロアカウント状態ごとに、個別にチックが存在します。マイクロアカウント状態の一部は、システム CPU 時間とシステム待ち時間に集計されます。残りの状態は、標本アナライザで個別に表示されます。

LWP が CPU のユーザーモードの場合は、通常生成されるチック配列には、ユーザー CPU 状態に対しては 1、他の状態に対してはゼロが含まれます。LWP が他の状態の場合は、チックは集計されますが、プロファイル信号はプロセスがユーザー CPU 状態に戻ったときに送信されます。

チックは整数で集計され、各チックは 1 つのプロファイル割り込み間隔を示します。LWP スケジューリングは、それよりも短時間の単位で行われます。したがって、プロファイルのパケットでの属性としては本質的に不安定です。通常は、すべての状態でのチックを合計して計算する LWP 時間の合計は、プロセスの gethrtime() が返す値と比較して、コンマ数パーセントまで同じです。CPU 時間は、プロセスの gethrvtime() が返す値と比較して、数パーセント程度異なる場合があります。負荷が高い場合は、差がさらに大きくなる場合があります。ただし、CPU 時間に差があっても系統的なひずみはなく、異なるルーチン、ソース行などに対してレポートされる相対時間は実質的にはひずみがありません。

gethrtime() および gethrvtime() については、これらの関数のマニュアルページを参照してください。


注 - 標本アナライザの表示する LWP 時間と vmstat の数値を比較する場合は、注意が必要です。標本アナライザの時間は、各 LWP のライフタイムの間のさまざまなマイクロ状態アカウント時間を合計した時間を示します。vmstat の表示する時間は、物理 CPU での時間を示します。たとえば、ターゲットプロセスの LWP 数が、LWP を実行するシステムの CPU 数よりも多い場合は、標本アナライザの示す待ち時間は vmstat の示す待ち時間よりも多くなります。CPU バウンドの LWP が 2 つで物理 CPU が 1 つという最も単純な例では、標本アナライザは 2 つの LWP の合計を示し、各 LWP の個別の待ち時間として約 50% を示します。vmstat は、アイドル時間がゼロであると示します。CPU は常にビジーですが、各 LWP の時間の半分は、他の LWP の実行中の待ち時間になります。

同期待ちの監視

同期待ちの監視イベントは、スレッドライブラリの関数呼び出しを追跡することで収集されます。イベントデータは、要求と許可 (追跡する呼び出しの最初と最後) の正確な時間データと、同期オブジェクト (要求される相互排他ロックなど) のアドレスで構成されます。要求と許可の時間差が、指定したしきい値を超えた場合にだけ、イベントが記録されます。同期トレースデータは、プロセスそのもので記録された時間と比較して、コンマ数パーセントの範囲で正確です。

ハードウェアカウンタのオーバーフロープロファイル

ハードウェアカウンタのオーバーフロープロファイルを使って、指定した LWP を実行している CPU のハードウェアカウンタおよびそのカウンタのオーバーフロー値 (増分数) を指定することができます。ハードウェアカウンタは、通常は、命令キャッシュミス、データキャッシュミス、クロックチック、実行された命令などを記録します。指定したカウンタがオーバーフロー値に到達すると、標本コレクタは LWP の呼び出しスタックを記録し、時間と、LWP およびその LWP 上で実行されているスレッドの ID を含めます。このデータは、標本アナライザで表示し、カウント測定結果のサポートに利用することができます。

ハードウェアカウンタはシステム固有であるため、選択可能なカウンタは使用するシステムによって異なります。多くのシステムは、ハードウェアカウンタのオーバーフロープロファイルをサポートしていません。その場合は、ハードウェアカウンタは使用することができません。

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

呼び出しスタックは、プログラム内からの命令を示す一連のプログラムアドレス (PC) です。最初の PC (リーフ PC と呼びます) は、スタックの最後に格納される、次に実行する命令のアドレスです。次の PC は、リーフ PC を含む関数の呼び出しのアドレスです。さらに、その関数の呼び出しのアドレスが次の PC というように、スタックの先頭まで続きます。呼び出しスタックの記録プロセスを、スタックの展開と呼びます (「スタックの展開」を参照)。

呼び出しスタックのリーフ PC は、その PC が含まれる関数にパフォーマンスデータの排他メトリックを関連付けます。スタックの他のすべての PC は、その PC が含まれる関数に包含メトリックを関連付けます。

ほとんどの場合は、記録された呼び出しスタックの PC はプログラムのソースコードでの記述通りに自然な形で関数に対応しており、標本アナライザの示す測定結果はそれらの関数に直接対応しています。ただし、プログラムの実際の実行が直観的な実行モデルとは異なり、標本アナライザの示す測定結果がわかりにくい場合があります。詳細は、「アドレスとプログラム構造のマッピング」を参照してください。

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

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

プログラムがメモリーに読み込まれて実行が開始されると、最初に実行するアドレス、初期レジスタセット、スタック (スクラッチデータの格納および関数の相互の呼び出し方法の記録用に使われるメモリー領域) を含めるコンテキストが確立されます。最初のアドレスは、すべての実行可能ファイルで組み込まれている _start() という関数に常に含まれています。

プログラムが実行されると、命令が順に実行されます。命令が呼び出し、ジャンプ、分岐に到達すると、分岐のターゲットが示すアドレスに制御が移動し、そこから実行が続行されます。

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

ほとんどの場合は、呼び出し先関数の最初の数個の命令のいずれかで、新しいフレームがスタックにプッシュされ、復帰アドレスがそのフレームに格納されます。復帰アドレス用に使われるレジスタは、呼び出し先関数が他の関数を呼び出すときに使うことができます。関数が復帰するときは、スタックからフレームをポップし、関数の呼び出し元のアドレスに制御が戻ります。

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

ある共有オブジェクト内の関数が別の共有オブジェクトの関数を呼び出す場合は、プログラム内での単純な関数呼び出しよりも複雑になります。各共有オブジェクトには、プログラムリンクテーブル (PLT) が 1 つずつ含まれています。PLT には、共有オブジェクトの外部にあり、その共有オブジェクトを参照するすべての関数のエントリが含まれています。最初は、PLT 内の各外部関数の実際のアドレスは、動的リンカーである ld.so 内のアドレスです。このような関数が最初に呼び出されると、制御が動的リンカーに移動します。動的リンカーは、その呼び出しから実際の外部関数を特定して、それ以降の呼び出しではその外部関数が使われるように PLT のアドレスを修正します。

シグナル

シグナルがプロセスに送信されると、シグナル送信時のリーフ PC をシステムルーチン sigacthandler() の呼び出しの復帰アドレスと見なすように、さまざまなレジスタおよびスタック操作が実行されます。sigacthandler() は、関数が他の関数を呼び出す場合と同様に、ユーザー指定のシグナルハンドラを呼び出します。標本アナライザは、この呼び出しのスタックフレームを通常と同様に処理します。ただし、スタックフレームは、任意の命令が呼び出しを生成できるように見せることができます。

高速トラップ

一部の命令は、カーネルに割り込み、軽量シグナルのユーザーモードに再度引き渡されます。これを高速トラップと呼びます。標本アナライザは高速トラップを認識しますが、SPARC-v9 で不正に割り当てられた整数メモリ参照は認識されません。その場合は、トラップ命令がハンドラを呼び出したものと見なされ、不正に割り当てられた整数トラップのフレームが表示されます。

カーネルトラップ

一部の命令は、カーネルに割り込み、カーネル内でエミュレートされます。たとえば、UltraSPARC-III プラットフォームの fitos 命令は、整数を単精度の浮動小数点数に変換します。標本アナライザでは特別な処理は実行されませんが、カーネルの処理が終了するまでトラップ命令は実行できないため、トラップ命令以降の命令の表示に時間がかかります。

テール呼び出し最適化

特定のルーチンが最後に実行する処理が他のルーチンの呼び出しである場合は、特別な最適化を実行することができます。呼び出し元は、実際に呼び出しを実行した後にスタックからフレームをポップして復帰する代わりに、スタックをポップしてからその呼び出し先を呼び出します。この最適化は、スタックのサイズ削減と、SPARC マシンでのレジスタウィンドウの使用削減が目的です。

たとえば、プログラムソースでは以下のような処理が指定されているとします。

A -> B -> C -> D

B および C でテール呼び出し最適化を実行すると、呼び出しスタックはプログラムが以下のような処理を実行するように見えます。

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

つまり、呼び出しツリーが平坦化されます。-g オプションを指定してコードをコンパイルすると、テール呼び出し最適化は O4 以上でだけ実行されます。-g オプションを指定しないでコンパイルすると、テール呼び出し最適化は O2 以上でだけ実行されます。

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

単純なプログラムは、単一の LWP (軽量プロセス) 上で単一スレッドで実行されます。マルチスレッド化した実行可能ファイルは、スレッド作成ルーチンを呼び出します。このルーチンは、スレッドを実行する別の LWP を作成します。オペレーティングシステムは、CPU への LWP の割り当てを制御して LWP を実行します。スレッドライブラリは、スレッドを LWP で実行するスケジュールを制御します。新しく作成されたスレッドは、_thread_start() というルーチンで実行が開始されます。このルーチンは、スレッドを作成する呼び出しで引き渡された関数を呼び出します。スレッド化は、各スレッドが特定の LWP に結合される結合スレッドか、各スレッドが異なる LWP、異なる時間にスケジュールされることがある非結合スレッドのいずれかを使って行います。

並列実行およびコンパイラ生成の本体関数

コードに Sun、Cray、OpenMP の並列化指示が含まれている場合は、並列実行用にコンパイルすることができます (OpenMP は、Fortran 95 で使用可能です。並列化戦略の基礎および OpenMP の指示については、『Fortran プログラミングガイド』の並列化および OpenMP の章を参照してください)。

ループまたは他の並列構造を並列実行用にコンパイルすると、コンパイラ生成コードは複数のスレッドによって実行され、マイクロタスクライブラリによって調整されます。

コンパイラが並列構造を検出すると、並列構造の本体を別の本体関数に配置し、並列構造をマイクロタスクライブラリのルーチンの呼び出しに置き換えることで、並列実行用コードを生成します。マイクロタスクライブラリのルーチンは、スレッドを振り分けて本体関数を実行します。本体関数のアドレスは、引数としてマイクロタスクライブラリのルーチンに引き渡されます。

コンパイラは、以下の形式の本体関数に名前を割り当てます。

_$1$mf_string1_$namelength$functionname$linenumber$string2

データを解析しやすくするため、標本アナライザは、コンパイラ生成名に加えて、本体関数により読みやすい名前を割り当てます。

実行時は、最初はメインスレッドだけが実行されます。最初に __mt_MasterFunction_() が呼び出されると、__mt_MasterFunction_() は複数のワークスレッドの作成を開始します。作成されるワークスレッドの個数は、環境変数 PARALLEL または OMP_NUM_THREADS で指定するか、OpenMP の実行時ルーチンである omp_set_num_threads() を呼び出して指定します。それ以降は、__mt_MasterFunction_() がマスタースレッドとワークスレッドの間の作業配分を管理します。

メインスレッドでは、__mt_MasterFunction_() は一連の振り分け関数を呼び出し、最終的には本体関数を呼び出します (これは、並列化用にコンパイルされたコードを CPU が 1 つのマシンで実行する場合や、CPU が複数のマシンでスレッドを 1 つだけ使う場合にも該当します)。

ワークスレッドは、Solaris のスレッドライブラリを使って作成します。ワークスレッドの呼び出しスタックは、_thread_start() というスレッドライブラリのルーチンを最初に呼び出します。_thread_start() は、__mt_SlaveFunction_() を呼び出します。この関数は、スレッドのライフタイムの間、スレッドによって継続して実行されます。__mt_SlaveFunction_() は、__mt_WaitForWork_() を呼び出し、作業が配分されるまでスレッドを待機させます。作業が配分されると、スレッドが __mt_SlaveFunction_() に戻り、本体関数の呼び出しを開始します。作業が終了すると、ワークスレッドは __mt_SlaveFunction_() に戻り、__mt_WaitForWork_() を再度呼び出します。スレッドの制御フローは、標本アナライザの「呼び出し元-呼び出し先」ウィンドウで確認することができます。このウィンドウの使い方については、「関数の呼び出し元と呼び出し先の測定結果の検査」を参照してください。


注 - 前述の呼び出し順序では、標本アナライザはコンパイラ生成関数の抽出元関数の呼び出しを示します。この呼び出しは、元の関数がコンパイラ生成関数を呼び出したように挿入され、包含的データは元の関数に対して表示されます。

ワークスレッドは、__mt_WaitForWork() では通常は CPU 時間を使って、作業が配分されたとき、つまりメインスレッドが新しい並列構造に到達したときの応答時間を短縮します (これをビジー待機と呼びます)。ただし、環境変数でスリープ待機を指定することもできます。この場合は、標本アナライザでは CPU 時間ではなく LWP 時間として表示されます。ワークスレッドが作業の配分を待つのは、一般的に以下の 2 つの場合です。プログラムを設計し直して、この待ち時間を削減することをお勧めします。

デフォルトでは、マイクロタスクライブラリは LWP に結合されたスレッドを使います。Makefile の FLAG 変数を UNBOUND に設定してからプログラムを構築するか、環境変数 MT_BIND_LWPFALSE に設定することで、デフォルト設定を無効にすることができます。

インデックスが long long のループは、インデックスが integer または long のループとは異なるマイクロタスクライブラリのルーチンを呼び出します。


注 - 多重処理振り分けプロセス全体は、システムに依存しません。また、将来のリリースでは変更される場合があります。

スタックの展開

標本コレクタがイベントを記録するときは、イベント発生時のプロセスの呼び出しスタックを記録します。記録される呼び出しスタックは、次に実行される命令のアドレス (リーフ PC)、復帰レジスタの内容、スタックの各フレームの復帰アドレスの内容で構成され、最後は _start() (メインスレッド用) および _thread_start() (ワークスレッド用) での呼び出し命令のアドレスになります。

標本コレクタは、常に復帰レジスタを記録します。標本アナライザは、発見的手法を使って、復帰アドレスがスタックにプッシュされているかどうかを特定します。プッシュされている場合は、復帰レジスタは無視されます。プッシュされていない場合は、復帰レジスタが呼び出し元 PC として使われます。フレームポインタと呼び特定のレジスタを使って、スタックの最初のフレームが特定されます。各フレームには、呼び出し元のフレームを特定するために使う前のフレームポインタが含まれています。Intel マシンの場合は、最適化されたコードでは、前のフレームポインタが各スタックフレームに含まれていないため、発見的手法を使ってスタックが展開されます。

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

標本アナライザは、呼び出しスタックを処理して PC 値を計算した後に、PC をプログラムの共有オブジェクト、関数、ソース行、逆アセンブリ行 (命令) にマッピングします。ここでは、このマッピングについて説明します。

プロセスイメージ

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

プロセスの最初のテキストセクションは、実行可能ファイルそのものから派生します。他のテキストセクシンは、プロセスの開始時に実行可能ファイルとともに読み込まれた、またはプロセスによって動的に読み込まれた、 共有オブジェクトに対応しています。呼び出しスタックの PC は、呼び出しスタックの記録時に読み込まれている実行可能ファイルおよび共有オブジェクトに基づいて解析されます。実行可能ファイルおよび共有オブジェクトは非常に似ていて、集合的にロードオブジェクトと呼ばれます。

共有オブジェクトは、プログラム実行中に読み込みおよび読み込み解除することができるため、実行中は時間が異なれば PC が指す関数も異なる場合があります。また、共有オブジェクトが読み込み解除された後に異なるアドレスで再度読み込まれた場合は、異なる PC が同一の関数を指す場合もあります。

ロードオブジェクトおよび関数

実行可能ファイルや共有オブジェクトにかかわらず、各ロードオブジェクトには、生成された命令を示すテキストセクション、データ用データセクション、さまざまなシンボルテーブルが含まれます。すべてのロードオブジェクトには、ELF シンボルテーブルが必要です。ELF シンボルテーブルには、そのオブジェクトの大域的な既知の関数すべての名前とアドレスが含まれます。-g オプションを指定してコンパイルしたロードオブジェクトには、追加のシンボル情報が含まれます。この情報は、ELF シンボルテーブルを拡張するもので、大域的でない関数に関する情報、関数の派生元のオブジェクトモジュールに関する情報、アドレスをソース行に関連付ける行番号を提供します。

関数という用語は、ソースコードで記述された高レベルの処理を示す命令セットを意味します。関数には、Fortran のサブルーチン、C++ のメソッドなども含まれます。関数は、ソースコードで明確に記述され、通常は、アドレスセットを示すシンボルテーブルで関数の名前が示されます。プログラムカウンタがアドレスセット内に含まれる場合は、プログラムのその関数が実行されています。

原則として、ロードオブジェクトのテキストセグメント内のアドレスは、関数にマッピングすることができます。まったく同一のマッピングが、呼び出しスタックのリーフ PC および他のすべての PC にも使われます。以下で説明する一部の関数を除き、関数はプログラムのソースモデルに直接対応しています。

エイリアス関数

通常、関数は大域的に定義されます。つまり、関数名はプログラムのすべての場所で既知になります。大域関数の名前は、実行可能なファイル内で一意にする必要があります。アドレス空間内に同一名の大域関数が複数存在する場合は、実行時リンカーはそれらの関数のいずれか 1 つだけに対する参照を有効にし、他の関数は実行されないため、参照されない関数は関数リストには表示されません。「概要メトリック」ウィンドウでは、選択した関数を含む共有オブジェクトおよびオブジェクトモジュールを確認することができます。

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

一意でない関数名

エイリアス関数は、コードの同一部分に対して複数の名前を使います。一方で、コードの複数の部分に同一名を使う場合があります。

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

静的関数は、ライブラリ内部での関数名がユーザーの使う関数名と衝突しないように、ライブラリ関数内でよく使われます。ライブラリをストリップすると、静的関数の名前は削除されます。この場合は、標本アナライザは <static>@0x12345 という形式の名前を生成します。ここで、@ 記号の後には、その関数のライブラリ内でのオフセットを示す文字列が付きます。標本アナライザは、隣接する複数のストリップ済み静的関数と単一のストリップ済み静的関数を区別できないため、複数のストリップ済み静的関数は測定結果がいっしょに表示される場合があります。

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

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

Fortran の代替エントリポイントのあるサブルーチンの処理は、コンパイラによって異なります。各エントリポイントの導入部のコードは、そのエントリポイント名を示すテキストの領域に常に対応しますが、ルーチンの本体のコードは、2 つのエントリポイント名のどちらかに関連付けられます。

インライン関数

インライン関数は、ソースでは関数として定義され、コンパイルすると実際の呼び出しの代わりに関数の呼び出し場所に挿入される命令になるコードです。インライン化には 2 種類あり、いずれも 標本アナライザに影響します。

いずれのインライン化も、パフォーマンス向上のために行われます。

C++ のインライン化を指定するには、メソッドの本体をそのメソッドのクラス定義に含めるか、メソッドをインライン関数として明示的に示します。このインライン化を行うのは、関数の呼び出しの方がインライン関数よりも処理時間がかかるためです。したがって、呼び出しを実行する代わりに、呼び出しを実行する場所に単に関数のコードを挿入する方が効率的です。アクセス関数では、必要な命令が 1 つだけの場合が多いため、通常はアクセス関数はインライン関数として定義されます。通常は、-g オプションを指定してコンパイルすると、インライン関数として定義した関数であっても、通常の関数としてコンパイルされます。ただし、C++ で -g0 というオプションを指定してコンパイルした場合は、他の最適化を指定しない場合でも、インライン関数として定義されたすべての関数がインライン関数としてコンパイルされます。

高レベルの最適化では、-g オプションを指定した場合でも、明示的および自動インライン化が実行されます。このインライン化を行うのは、関数呼び出しの時間を節約するための場合もありますが、多くの場合は、レジスタを使う命令をより多く提供し、命令のスケジューリングの最適化を行うことが目的です。

いずれのインライン化も、関数リストでは同様に表示されます。ソースコードには記述されているがインライン化された関数は、関数リストでは表示されず、インライン関数の呼び出し場所で通常であれば包含的な測定結果と見なされる結果 (呼び出し先関数で消費された時間を示します) は、実際は排他的な測定結果 (呼び出し場所に挿入されたインライン関数の命令を示します) として表示されます。


注 - 多くの場合は、インライン化によってデータの解析が難しくなります。したがって、パフォーマンス解析を行うときはインライン化を無効にすることをお勧めします。

場合によっては、関数がインライン化されていても、いわゆる行の範囲外の関数が残る場合があります。ある呼び出し場所では行の範囲外の関数が呼び出され、別の場所では命令がインライン化されることがあります。このような場合は、関数はインライン化されていないように関数リストで表示されますが、その測定結果は行の範囲外の呼び出しだけを反映しています。

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

コンパイラが関数内のループまたは並列化指示のある領域を並列化すると、プログラムのソースモデルでは明示的に現れない新しい本体関数を作成します。このような本体関数の詳細は、「並列実行およびコンパイラ生成の本体関数」を参照してください。

標本アナライザは、このような本体関数を通常の関数として表示し、コンパイラ生成名に加えて、抽出元の関数に基づくラベルを付けます。これらの関数の排他的および包含的な測定結果は、本体関数で消費された時間を示します。また、構造の抽出元関数は、各本体関数の包含的な測定結果を示します。

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

アウトライン関数

フィードバック最適化の際に、アウトライン関数が作成されることがあります。アウトライン関数は、通常は実行されないコードを示します。特に、フィードバック生成に使われる「試験実行」の際に実行されないコードを示します。ページングおよび命令キャッシュの動作を向上するため、アウトライン関数はアドレス空間の別の場所に移動され、以下の形式の名前の関数に変換されます。

_$1$outlinestring1$namelength$functionname$linenumber$string2

アウトライン関数は、通常の関数として、適切な包含的および排他的な測定結果とともに表示されます。また、アウトライン関数の測定結果は、元の関数での包含的測定結果として追加されます。

コンパイラ生成の本体関数と同様に、標本アナライザはアウトライン関数の元の関数からの呼び出しを表示します。

<未知> 関数

場合によっては、PC が既知の関数にマッピングされないことがあります。このような場合は、PC は <未知> という特別な関数にマッピングされます。

以下の場合は、PC が <未知> にマッピングされます。

<合計> 関数

<合計> 関数は、プログラム全体を示すための構造です。すべてのパフォーマンス測定結果は、呼び出しスタックの関数にマッピングされる以外に、<合計> という特別な関数にもマッピングされます。この関数は、関数リストの最初に表示され、そのデータを使って他の関数のデータを概観することができます。

「呼び出し元-呼び出し先」ウィンドウ

ここでは、「呼び出し元-呼び出し先」ウィンドウと、このウィンドウでのプログラム実行の表示について説明します。

<合計> 関数

特別な関数である <合計> は、プログラム実行のメインスレッドでの _start() の名目上の呼び出し元、および作成されたスレッドの _thread_start() の名目上の呼び出し元として表示されます。

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

Fortran の代替エントリポイントのあるサブルーチンでの時間を示す呼び出しスタックは、通常は導入部ではなくサブルーチンの本体に PC があり、本体に関連付けられた名前だけが呼び出し先として表示されます。いずれの場合も、標本アナライザは、収集されたデータを使ってメインのエントリポイントの呼び出しと代替エントリポイントの呼び出しを区別することができません。

同様に、サブルーチンからのすべての呼び出しは、サブルーチンの本体に関連付けられた名前から作成されたものとして表示されます。

インライン関数

インライン関数は、インライン化後のルーチンの呼び出し先としては表示されません。いずれかの場所でインライン化されたが、別の場所の通常の関数として表示される関数のデータを解析するときは注意してください。標本アナライザでは、通常の関数の測定結果だけが表示されます。この結果は、その関数のすべてのインスタンス (インライン化されたものと通常のもの) の合計の測定結果の一部だけを示している場合があります。

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

コンパイラ生成の本体関数は、「並列実行およびコンパイラ生成の本体関数」で説明するように、マイクロタスクライブラリのルーチンによって直接呼び出されます。ただし、標本アナライザで表示される動作を実行のソースモデルに近づけるため、標本アナライザはループルーチンの抽出元関数からの疑似呼び出しを抽出元の行で表示します。したがって、標本アナライザでは、本体のルーチンの抽出元関数が呼び出し元として表示され、その関数に対する包含的時間が測定されます。

アウトライン関数

アウトライン関数は、実際には呼び出し先ではなくジャンプ先になります。同様に、復帰ではなくジャンプ先から戻ることになります。動作をユーザーのソースモデルに近づけるため、標本アナライザはメインルーチンからの疑似呼び出しをそのアウトライン部分に関連付けます。

テール呼び出し最適化

テール呼び出し最適化が行われた中間呼び出しは、「呼び出し元-呼び出し先」ウィンドウでは明示的に表示されません。

シグナル

標本アナライザは、シグナル送信で発生したフレームを通常のフレームとして処理します。シグナルの送信時のユーザーコードがシステムルーチン sigacthandler() を呼び出し、sigacthandler() がユーザーのシグナルハンドラを呼び出すように表示されます。sigacthandler() およびユーザーのシグナルハンドラの両方、およびそれらが呼び出す他の関数の包含的な測定結果は、割り込まれたルーチンの包含的な測定結果として表示されます。

ストリップ済み静的関数

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

<未知> 関数

<未知> 関数の呼び出し元および呼び出し先は、呼び出しスタックの前および次の PC を示し、通常と同様に処理されます。

再帰呼び出し

再帰呼び出しとは、関数がそれ自身を呼び出すことです。「呼び出し元-呼び出し先」ウィンドウでは、再帰関数は呼び出し先ではなくそれ自身の呼び出し元として表示されます。

注釈付きソースコードおよび逆アセンブリコード

標本アナライザの注釈付きソースコードおよび逆アセンブリコードの機能は、関数内の処理が原因で性能が低下する場合に便利です。

注釈付きソースコード

注釈付きソースは、ソース行レベルでのアプリケーションの資源消費を示します。注釈付きソースは、アプリケーションの呼び出しスタックに記録された PC をソース行にマッピングすることで作成されます。標本アナライザは、注釈付きソースファイルを作成するため、最初に特定のオブジェクトモジュール (.o ファイル) で生成されたすべての関数を特定し、各関数の PC すべてのデータを調べます。注釈付きソースを作成するには、標本アナライザがすべてのオブジェクトモジュールまたはロードオブジェクトを検出して読み込み、PC とソース行のマッピングを特定できる必要があります。また、表示用に、ソースファイルを読み込み、注釈付きソースのコピーを作成できる必要があります。

コンパイル処理では、要求される最適化のレベルに応じて多くの段階があり、プログラム変換によって命令とソース行のマッピングに混乱が生じることがあります。最適化によっては、ソース行の情報が完全に失われたり混乱が生じたりすることがあります。コンパイラは、さまざまな発見的手法によってソース行の命令を追跡しますが、発見的手法は完全ではありません。

表 6-1 に、注釈付きソースコードの行で表示される 4 種類の測定結果を示します。

表 6-1   注釈付きソースコードの測定結果 
測定
結果
意味
(空白) この行に対応する PC がプログラムに存在しません。コメント行では常にこの結果になります。また、以下の場合のコード行でも同様の結果になります。

  • コード行の命令がすべて最適化され取り去られた場合

  • 別の場所とコードが一致していて、コンパイラが共通部分式を認識し、その行の全部の命令を別の行に結びつけた場合

  • コンパイラがその行の命令に間違った行番号結びつけた場合

  • 0. この行に対応する PC がプログラムに存在しますが、その PC を参照するデータがありません。統計的に標本採取された呼び出しスタックまたはスレッド同期データのために追跡された呼び出しスタックに、この PC が存在しないことを示します。測定結果が 0. の場合は、行が実行されなかったことを示すのではなく、統計上プロファイルに表れないこと、およびその行のスレッド同期呼び出しでしきい値を超える遅延がなかったことを示します。
    0.000 この行の PC の少なくとも 1 つがデータに表れているが、測定結果の合計値が丸められてゼロになったことを示します。
    1.234 この行のすべての PC の測定結果の合計がゼロ以外になり、数値として表示されています。


    コンパイラのコメント

    コンパイラのさまざまな部分で、実行可能ファイルにコメントが挿入されることがあります。各コメントは、ソースの特定の行に対応しています。

    一部のコメントは、f95 コンパイラによって挿入されます。これらのコメントは、配列セクションをサブルーチンに引き渡す際に必要なコピーインおよびコピーアウトが原因のパフォーマンス低下の可能性を示します。コードを並列化の解析用にコンパイルした場合は、ループの並列化状態を示すコメントも挿入されます。

    注釈付きソースの書き込み時には、ソース行に対してコンパイラが生成するコメントがソース行の直前に挿入されます。

    不明行 - <sum of all instructions without line numbers>

    PC に対応するソース行を特定できない場合は、その PC の測定結果は、注釈付きソースファイルの最初に挿入される特別なソース行に関連付けられます。このソース行の測定結果が高い場合は、オブジェクトモジュールのコードの一部に行がマッピングされていないことを示します。注釈付き逆アセンブリを使って、マッピングのない命令の内容を特定することができます。

    共通部分式の除去

    最も一般的な最適化では、複数の場所に出現する同一の式を検出し、その式のコードを一カ所に集めることでパフォーマンスを向上します。たとえば、コードの同一ブロックの ifelse の分岐の両方で同一の処理が記述されている場合は、コンパイラはその処理を if の前に移動します。その場合は、コンパイラはそれ以前に出現する同一式のいずれかを基準にして、命令に行番号を割り当てます。割り当てられた行番号が if 構造の一方の分岐に対応していて、実際にはもう一方の分岐が常に実行される場合は、注釈付きソースでは、実行されない分岐内の行の測定結果が表示されます。

    注釈付き逆アセンブリ

    注釈付き逆アセンブリは、各命令に対応するパフォーマンス測定結果が挿入された、関数またはオブジェクトモジュールの命令のアセンブリコードを提供します。命令の出現頻度が高いほど、その関数に消費される時間は多くなります。注釈付き逆アセンブリは、行番号マッピングおよびソースファイルが使用可能かどうか、および注釈付き逆アセンブリが要求される関数のオブジェクトモジュールが既知かどうかによって、複数の方法のいずれかで表示されます。

    コードが最適化されていない場合は、行番号は単純で、ソースおよび逆アセンブルされた命令はそのまま表示されます。最適化されている場合は、後の命令が前の行よりも前に表示されることがあります。標本アナライザの挿入アルゴリズムでは、命令が行 N に記述されている場合は、行 N までのすべてのソース行をその命令の前に挿入します。ソースの行 N に対応するコンパイラのコメントは、その行の直前に挿入されます。

    逆アセンブリコードの各命令には、以下の注釈が付きます。

    可能な場合は、呼び出しアドレスがシンボルに変換されます。命令の行には測定結果が表示されますが、挿入されたソースまたはコメントには表示されません。表示される測定結果の値については、表 6-1 に示すソースコードの注釈を参照してください。

    パフォーマンスコストについて

    関数レベル、ソース行レベル、逆アセンブリ命令レベルで、測定結果の値を調べることができます。各レベルの測定結果の値が高い場合は、別の方法でコードを改良して効率を向上することができます。

    関数レベルのパフォーマンス

    関数の測定結果は、実行回数が多い、あるいは 1 回の実行にかかる時間が長い場合に値が高くなります。

    通常は、関数の注釈付きソースを調べることで、パフォーマンスの向上方法を特定するのが最も簡単です。

    ソース行レベルのパフォーマンス

    注釈付きソースで測定結果の値が高い行は、関数で実行時間の大半が消費されている部分を示しています。アルゴリズムを改良または見直すか、関数の最適化レベルを高くすることで、パフォーマンスが向上する可能性があります。アルゴリズムが効率的で最適化にも問題がない場合は、注釈付き逆アセンブリを調べることで、パフォーマンスの向上方法を特定できることがあります。

    命令レベルのパフォーマンス

    通常は、命令レベルでの効率的なコードの生成はコンパイラが行います。場合によっては、特定のリーフ PC の示す命令の実行前に遅延があり、出現頻度が高くなることがあります。また、命令がカーネルに割り込むときなどのように、前の命令の実行に時間がかかり、割り込みが不可能なため、特定のリーフ PC が出現することがあります。

    命令の実行遅延には複数の原因があり、いずれもパフォーマンス向上につながる可能性があります。命令の実行遅延は、数値命令がレジスタを必要とするが、そのレジスタの内容が前の命令によってセットされていて、前の命令がまだ終了していないために発生することがあります。このような遅延の例として、データキャッシュミスのある読み込み命令や、実行に 1 サイクル以上かかる浮動小数点数の除算などの浮動小数点数演算命令などがあります。

    命令を含むメモリーワードが命令キャッシュに含まれていない場合にも、命令の出現頻度が過度に高くなることがあります。ある命令が常に前の命令と同一のクロックで実行され、次に実行される命令として表示されない場合には、出現頻度が過度に低くなることがあります。


    サン・マイクロシステムズ株式会社
    Copyright information. All rights reserved.
    ホーム   |   目次   |   前ページへ   |   次ページへ   |   索引