Sun Studio 12: パフォーマンスアナライザ

第 8 章 注釈付きソースと逆アセンブリデータについて

注釈付きソースコードと注釈付き逆アセンブリコードは、関数内のパフォーマンス低下の原因になっているソース行または命令を特定するのに役立ち、コンパイラがコードに対して行なった変換処理に関するコメントを表示します。ここでは、注釈の処理と、注釈付きコードを解釈する上での問題点をいくつか説明します。

注釈付きソースコード

実験の注釈付きソースコードは、パフォーマンスアナライザで「アナライザ」ウィンドウの左の区画にある「ソース」タブを選択することで表示できます。または、実験を実行しなくても、er_src ユーティリティーを使用して注釈付きソースコードを表示できます。ここでは、パフォーマンスアナライザでソースコードを表示する方法について説明します。er_src ユーティリティーを使用した注釈付きソースコードの表示の詳細は、「実験なしのソース/逆アセンブリの表示」を参照してください。

アナライザの注釈付きソースには、次の情報が含まれています。

パフォーマンスアナライザの「ソース」タブのレイアウト

「ソース」タブは、いくつかの列に分かれています。ウィンドウの左側には個々のメトリックを表示する固定幅の列が表示されます。右側の残りの列には、注釈付きソースが表示されます。

元のソース行の識別

注釈付きソースで黒字で表示されるすべての行は、元のソースファイルから取得されたものです。注釈付きソース列の各行の先頭の番号は、元のソースファイルの行番号に対応します。別の色で文字が表示されている行は、インデックス行かコンパイラのコメント行です。

「ソース」タブのインデックス行

ソースファイルとは、オブジェクトファイルを生成するためにコンパイルされた、またはバイトコードにインタプリタ処理されたファイルです。オブジェクトファイルには通常、ソースコード内の関数、サブルーチン、またはメソッドに対応する実行可能コードの領域が 1 つ以上含まれています。アナライザはオブジェクトファイルを解析して、それぞれの実行可能領域を関数として識別します。そして、オブジェクトコード内で見つけた関数を、オブジェクトコードに関連付けられたソースファイル内の関数、ルーチン、サブルーチン、またはメソッドにマップしようとします。アナライザは解析が成功すると、注釈付きソースファイル内の、オブジェクトコードで検出された関数の最初の命令に対応する場所に、インデックス行を追加します。

注釈付きソースは、「関数」タブのリストにインライン関数が表示されていない場合でも、インライン関数を含むすべての関数のインデックス行を表示します。「ソース」タブには、インデックス行が赤いイタリック体で、テキストが山括弧内に示されます。もっとも単純な種類のインデックス行は、関数のデフォルトコンテキストに対応します。関数のデフォルトソースコンテキストは、その関数の最初の命令が帰するソースファイルとして定義されます。次の例は、C 関数 icputime のインデックス行を示しています。


0.       0.         600. int
0.       0.         601. icputime(int k)
0.       0.         602. {
0.       0.              <関数: icputime>

この例で分かるように、インデックス行は最初の命令に続く行に表示されます。C ソースの場合は、最初の命令は、関数本体の先頭が開く括弧に対応します。Fortran ソースの場合は、各サブルーチンのインデックス行が、subroutine キーワードを含む行に続きます。また、次の例に示すように、main 関数のインデックス行が、アプリケーションの起動時に実行される最初の Fortran ソース命令に続きます。


0.       0.       1. ! Copyright 02/04/2000 Sun Microsystems, Inc. All Rights Reserved
0.       0.       2. !
0.       0.       3. ! Synthetic f90 program, used for testing openmp directives and 
0.       0.       4. ! the analyzer
0.       0.       5.
0.       0.       6. !$PRAGMA C (gethrtime, gethrvtime)
0.       0.       7.
0.       81.497    [  8] 9000    format(’X ’, f7.3, 7x, f7.3, 4x, a)
0.       0.             <関数: main>

場合によっては、アナライザは、オブジェクトコード内に見つけた関数を、そのオブジェクトコードに関連付けられたソースファイル内のプログラミング命令を使ってマップできないことがあります。たとえば、ヘッダーファイルのように、コードが #included されている場合や、ほかのファイルからインライン化されている場合があります。

オブジェクトコードのソースが、オブジェクトコードに含まれている関数のデフォルトソースコンテキストでない場合は、オブジェクトコードに対応する注釈付きソースには、関数のデフォルトソースコンテキストを相互参照する特別なインデックス行が含まれます。たとえば、synprog デモをコンパイルすると、ソースファイル endcases.c に対応するオブジェクトモジュール endcases.o が作成されます。endcases.c 内のソースは、ヘッダー inc_func.h 内で定義されている関数 inc_func を宣言します。ヘッダーには、このようにしてインライン関数定義が含まれることがよくあります。endcases.c は関数 inc_func を宣言しますが、endcases.c 内のソース行で inc_func の命令に対応するものはないため、endcases.c の注釈付きソースファイルの先頭には、次のような特別なインデックス行が示されます。


0.650     0.650       <関数: inc_func, ソースファイル inc_func.h から得られた命令>

このインデックス行のメトリックは、endcases.o オブジェクトモジュールのそのコード部分の行は (ソースファイル endcases.c 内の) マッピングが行われていないことを示します。

アナライザはまた、関数 inc_func が定義されている inc_func.h の注釈付きソースに標準インデックス行も追加します。

.


0.       0.         2.
0.       0.         3. void
0.       0.         4. inc_func(int n)
0.       0.         5. {
0.       0.            <関数: inc_func>

同様に、関数に代替ソースコンテキストがある場合は、そのコンテキストを相互参照するインデックス行が、デフォルトソースコンテキストの注釈付きソースに表示されます。


0.            0.        142. inc_body(int n)
0.650     0.650       <関数: inc_body, ソースファイル inc_body.h から得られた命令>
0.            0.        143. {
0.            0.                  <関数: inc_body>
0.            0.        144. #include "inc_body.h"
0.            0.        145. }

別のソースコンテキストを参照するインデックス行をダブルクリックすると、そのソースファイルの内容がソースウィンドウに表示されます。

また、特別なインデックス行やコンパイラのコメントではない特殊な行は、赤で示されます。たとえば、コンパイラの最適化の結果、ソースファイルに記述されているコードに対応しないオブジェクトコード内の関数について、特別なインデックス行が作成される場合があります。詳細は、「「ソース」タブ、「逆アセンブリ」タブ、「PC」タブの特別な行」を参照してください。

コンパイラのコメント

コンパイラのコメントは、コンパイラによって最適化されたコードがどのように生成されたかを示します。インデックス行や元のソース行と区別できるように、コンパイラのコメント行は青く表示されます。コンパイラのさまざまな段階で、実行可能ファイルにコメントが挿入されることがあります。各コメントは、ソースの特定の行に関連付けられます。注釈付きソースの書き込み時には、ソース行に対してコンパイラが生成するコメントが、ソース行の直前に挿入されます。

コンパイラのコメントは、最適化するためにソースコードに対して行われた変換の大部分に関する情報を提供します。こうした変換には、ループの最適化、並列化、インライン化、パイプライン化などがあります。次に、コンパイラのコメントの例を示します。


                    関数 freegraph はソースファイル ptraliasstr.c から次の行へ
              インライン化されました
0.       0.         47.       freegraph();
                    48.    }
0.       0.         49.    for (j=0;j<ITER;j++) {

                    関数 initgraph はソースファイル ptraliasstr.c から次の行へ
                    インライン化されました
0.       0.         50.       initgraph(rows);
        
                    関数 setvalsmod はソースファイル ptraliasstr.c から次の行へ
                    インライン化されました
              以下のループは 2 個のループに分裂しました
              以下のループは 51 行目のループと融合しました
              以下のループは展開および/あるいは並列化の向上のため、
                             繰り返しを省略しました
              以下のループは定常サイクル数 = 3 でスケジュール化されました
              以下のループは 8 回、展開されました
              以下のループは繰り返しにつき 0 のロード、3 のストア、
                    3 の先読み、0 の FPadds、0 の FPmuls、0 の FPdivs を行います
                    51.       setvalsmod();

関数 setvalsmod() にはループコードが含まれており、この関数はインライン化されているため、51 行目のコメントにはループコメントが含まれています。

「ソース」タブに表示されるコンパイラコメントの種類は、「データ表示方法の設 定」ダイアログボックスの「ソース/逆アセンブリ」タブを使って設定できます。詳細は、「データ表示オプションの設定」を参照してください。

共通部分式の除去

非常に一般的な最適化の例として、1 つの式が複数の場所に存在するときに、この式のコードを 1 つの場所にまとめることによってパフォーマンスを向上できます。たとえば、コードブロックの ifelse の分岐の両方で同じ演算が記述されている場合、コンパイラはその演算を if 文の直前に移動することができます。実際にそのようにした場合、コンパイラは以前出現した一方の式に基づいて、命令に行番号を割り当てます。割り当てられた行番号が if 構造の分岐の 1 つに対応していて、実際にはもう一方の分岐が常に実行される場合、注釈付きソースでは、実行されない分岐内の行のメトリックが表示されます。

ループの最適化

コンパイラは、数種類のループ最適化を行うことができます。 一般的なものを次に示します。

ループの展開では、ループ本体内でループを数回反復し、それに応じてループインデックスを調整します。ループの本体が大きくなるほど、コンパイラはより効率的に命令をスケジュールできます。また、ループインデックスの増分や条件検査操作によるオーバーヘッドが減少します。残りのループは、ループのピーリングを使って処理されます。

ループのピーリングでは、ループから多数のループの反復を取り除き、これらをループの前か後ろに適宜移動します。

ループの入れ換えは、メモリーのストライドを最小限に抑えてキャッシュ行のヒット率を最大限に上げるために、入れ子のループの順序を変更します。

ループの融合は、隣り合ったループや近接したループを 1 つのループにまとめます。ループの融合からは、ループの展開と同じような利点がもたらされます。さらに、最適化済みの 2 つのループで共通のデータにアクセスする場合は、ループの融合によってループのキャッシュの局所性が改善されて、コンパイラは命令レベルの並列化機能をさらに活用することが可能になります。

ループの分散はループの融合の反対で、ループは複数のループに分割されます。この最適化は、ループ内の計算回数が過度に多くなり、パフォーマンス低下の原因となるレジスタのスピルが発生する場合に適しています。また、ループの分裂は、ループに条件文が含まれている場合にも有効です。場合によっては、条件文を含むものと含まないものの 2 つにループを分割できます。これによって、条件文を含まないループにおけるソフトウェアのパイプライン化の機会が増えます。

場合によって、入れ子のループでは、コンパイラはループの分裂を適用してループを分割し、次にループの融合を実行して異なる方法でループをまとめ直すことでパフォーマンスを改善します。この場合は、次のようなコンパイラのコメントが表示されます。


    以下のループは 2 個のループに分裂しました
    以下のループは 116 行目のループと融合しました
    [116]    for (i=0;i<nvtxs;i++) {

関数のインライン化

インライン関数を使用して、コンパイラは、実際の関数呼び出しを行う代わりに、関数が呼び出された場所に関数の命令を直接挿入します。つまり、C/C++ マクロと同様に、それぞれの呼び出し場所でインライン関数の命令の複製が作成されます。コンパイラは、高レベルの最適化 (4 および 5) で明示的または自動的なインライン化を実行します。インライン化によって関数呼び出しの負荷が減り、レジスタの使用や命令のスケジュールを最適化するための命令がさらに提供されますが、その代わりに、コードのメモリー使用量が多くなります。次に、コンパイラコメントのインライン化の例を示します。


                関数  initgraph はソースファイル ptralias.c から次の行の
                    コードへインライン化されました
0.       0.         44.       initgraph(rows);

注 –

コンパイラのコメントは、アナライザの「ソース」タブ内で 2 行にまたがって折り返されることはありません。


並列化

Sun、Cray、または OpenMP の並列化指令が含まれているコードの場合、複数プロセッサ上での並列実行用にコンパイルできます。コンパイラのコメントは、並列化が実行されている場所と実行されていない場所とその理由を示します。次に、並列化コンピュータのコメントの例を示します。


0.       6.324       9. c$omp  parallel do shared(a,b,c,n) private(i,j,k)
0.       0.    
                   以下のループは明示的なユーザー指令によって並列化されました
                   以下のループは 12 行目のループと交換されました
0.010    0.010     [10]            do i = 2, n-1

                   以下のループは並列化ループ中でネストされていたため、
                   並列化されていません
                   以下のループは 12 行目のループと交換されました
0.170    0.170      11.               do j = 2, i

並列実行とコンパイラ生成の本体関数の詳細は、「OpenMP ソフトウェアの実行の概要」を参照してください。

注釈付きソースの特別な行

「ソース」タブには、特殊な場合のためのほかの注釈を表示できます。これらの注釈は、コンパイラのコメントの形で、またはインデックス行と同じ色で特別な行に表示できます。詳細は、「「ソース」タブ、「逆アセンブリ」タブ、「PC」タブの特別な行」を参照してください。

ソース行メトリック

実行可能コードの各行のソースコードメトリックは、固定幅の列に表示されます。メトリックは、関数リストのものと同じです。実験のデフォルト値は、.er.rc ファイルを使って変更できます。詳細は、「デフォルト値を設定するコマンド」を参照してください。また、表示されるメトリックとしきい値の強調表示も、「データ表示方法の設定」ダイアログボックスを使ってアナライザで変更できます。詳細は、「データ表示オプションの設定」を参照してください。

注釈付きソースコードは、ソース行レベルでのアプリケーションのメトリックを示します。注釈付きソースは、アプリケーションの呼び出しスタックに記録された PC (プログラムカウント) を読み取り、各 PC をソース行にマッピングすることによって作成されます。注釈付きソースファイルを作成するために、アナライザは、最初に特定のオブジェクトモジュール (.o ファイル) またはロードオブジェクト内に生成されたすべての関数を特定し、各関数のすべての PC のデータをスキャンします。注釈付きソースを作成するには、アナライザが、すべてのオブジェクトモジュールまたはロードオブジェクトを検出して読み取り、PC からソース行へのマッピングを特定できる必要があります。また、表示するソースファイルを読み取って、注釈付きのコピーを作成できる必要もあります。アナライザはソースファイル、オブジェクトファイル、および実行可能ファイルを次のデフォルトの場所で順に検索し、正しいベース名のファイルが見つかると検索を停止します。

デフォルト値は、addpath または setpath 指令、あるいはアナライザ GUI によって変更できます。

addpath または setpath によって設定されたパスのリストを使用してファイルを検出できない場合は、pathmap コマンドを使用して 1 つ以上のパス再マッピングを指定できます。pathmap コマンドを使用して、old-prefixnew-prefix を指定できます。ソースファイル、オブジェクトファイル、または共有オブジェクトのパス名が old-prefix で指定した接頭辞で始まる場合、古い接頭辞は new-prefix で指定した接頭辞に置き換えられます。結果のパスは、ファイルの検索に使用されます。複数の pathmap コマンドが提供されており、それぞれが、ファイルが見つかるまで試行されます。

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

ソース行メトリックの解釈

命令のメトリックについては、実行対象の命令を待っている間に発生したメトリックとして解釈する必要があります。イベントが記録されるときに実行中である命令がリーフ PC と同じソース行に存在している場合、メトリックはこのソース行を実行した結果であると解釈できます。ただし、実行中の命令とリーフ PC が存在しているソース行がそれぞれ異なる場合、リーフ PC が存在しているソース行のメトリックの少なくとも一部は、実行中命令のソース行が実行待ちしていた間に集計されたメトリックであると解釈する必要があります。この一例としては、1 つのソース行で計算された値が次のソース行で使用される場合が挙げられます。

メトリックの解釈方法がもっとも問題となるのは、キャッシュミスやリソース待ち行列ストールなど、実行が大幅に遅延している場合や、命令が直前の命令の結果を待っている場合です。このような場合、ソース行のメトリックが異常に高く見えることがあります。コード内のほかのソース行を調べて、こういった高メトリック値の原因である行を付き止めてください。

メトリックの形式

表 8–1 に、注釈付きソースコードの行に表示可能な 4 種類のメトリックをまとめました。

表 8–1 注釈付きソースコードのメトリック

メトリック 

意味 

(空白) 

プログラムに、このコード行に対応する PC が存在しません。コメント行は常にこの空白になります。また、次の場合の見かけ上のコード行も空白になります。 

  • 最適化中に、見かけ上のコード部分のすべての命令が削除されている。

  • コードが別の場所で繰り返されていて、コンパイラによって共通する部分式が認識され、その行のすべての命令に繰り返し部分の行番号が付けられている。

  • コンパイラによって、その行の命令に不正な行番号が付けられている。

0.

プログラム内のいくつかの PC がこの行から派生したとしてタグ付けされましたが、それらの PC を参照するデータがありません。統計的に標本収集されたかトレースされた呼び出しスタックに、そうした PC は存在しません。0. メトリックは、その行が実行されなかったことを意味するのではなく、プロファイリングデータパケットや記録されたトレースデータパケットに統計として表示されなかったことだけを意味します。

0.000

この行の少なくとも 1 つの PC がデータに表れていますが、メトリック値の計算でゼロに丸められました。 

1.234

この行に属するすべての PC のメトリックの合計が、表示されているゼロ以外の数値になりました。 

注釈付き逆アセンブリコード

注釈付き逆アセンブリは、関数またはオブジェクトモジュールの命令のアセンブリコードのリストを提供します。このリストには、各命令に関連付けられているパフォーマンスメトリックが含まれています。注釈付き逆アセンブリは複数の方法で表示することができ、どの方法で表示されるかは、行番号のマッピング情報およびソースファイルが存在するかどうか、また注釈付き逆アセンブリが要求されている関数のオブジェクトモジュールが既知かどうかによって決まります。

逆アセンブリコードの各命令には、注釈として次の情報が付けられます。

呼び出しアドレスの解決が可能な場合、それらのアドレスは関数名などのシンボルに変換されます。メトリックは、命令行について表示されます。対応する表示方式が設定されていれば、インタリーブされるソースコードについても表示することができます。表示可能なメトリック値は、表 8–1 で示しているソースコードの注釈で説明しているとおりです。

複数の場所に #included で取り込まれているコードの逆アセンブリリストでは、コードの #included のたびに逆アセンブリ命令が 1 回繰り返されます。ソースコードは、逆アセンブリコードの繰り返しブロックがファイルに最初に示されたときのみインタリーブされます。たとえば、inc_body.h というヘッダーに定義されているコードブロックが、inc_body inc_entryinc_middle、および inc_exit という 4 つの関数によって #included されている場合、逆アセンブリ命令のブロックは inc_body.h の逆アセンブリリストに 4 回現れますが、ソースコードは逆アセンブリ命令の 4 つのブロックの最初のものにのみインタリーブされます。「ソース」タブに切り替えると、逆アセンブリコードの毎回の繰り返しに対応するインデックス行が表示されます。

インデックス行は「逆アセンブリ」タブ内に表示される場合があります。「ソース」タブの場合とは異なり、これらのインデックス行を直接移動目的に使用することはできません。ただし、インデックス行の直下の命令の 1 つにカーソルを置いて「ソース」タブを選択すると、インデックス行で参照されているファイルに移動できます。いんたり

ほかのファイルのコードを #include するファイルでは、ソースコードのインタリーブなしで、インクルードされたコードが逆アセンブリ命令として表示されます。ただし、これらの命令の 1 つにカーソルを置いて「ソース」タブを選択すると、#included されているコードを含むファイルが開かれます。このファイルを表示した状態で「逆アセンブリ」タブを選択すると、インタリーブされたソースコードとともに逆アセンブリコードが表示されます。

インライン関数の場合はソースコードと逆アセンブリコードをインタリーブできますが、マクロの場合はできません。

コードが最適化されていない場合、各命令の行番号は逐次順であり、ソース行と逆アセンブリされた命令は予想どおりにインタリーブされます。最適化されている場合は、あとの行の命令が前の行の命令よりも前に表示されることがあります。アナライザのインタリーブアルゴリズムでは、命令が行 N にあると表示される場合は、常に、行 N とその行までのすべてのソース行がその命令の前に挿入されます。最適化を行なった結果、制御転送命令とその遅延スロット命令の間にソースコードが現れます。ソース行の N に関連するコンパイラのコメントは、その行の直前に挿入されます。

注釈付き逆アセンブリの解釈

注釈付き逆アセンブリコードを解釈するのは簡単ではありません。リーフ PC とは、次に実行する命令のアドレスです。このため、命令の属性メトリックは、命令の実行待ちに費やされた時間とみなされます。ただし、命令の実行は必ずしも順に行われるわけではなく、呼び出しスタックの記録に遅延があることもあります。注釈付き逆アセンブリコードを利用するには、実験の記録先であるハードウェアと、そのハードウェアが命令を読み込み、実行する方法を理解しておいてください。

次では、注釈付き逆アセンブリコードを解釈する上での問題点をいくつか説明します。

命令発行時のグループ化

命令は、命令発行グループとして知られるグループ単位で読み込まれ、発行されます。グループに含まれる命令は、ハードウェア、命令の種類、すでに実行された命令、およびほかの命令またはレジスタの依存関係によって異なります。その結果、ある命令が常に前の命令と同じクロックで実行され、次に実行される命令として現れない場合、その命令の出現回数は実際よりも少なくなることを意味します。また、呼び出しスタックが記録されるときに、「次」に実行する命令が複数存在する可能性もあります。

命令発行規則はプロセッサの種類ごとに異なり、キャッシュ行内の命令位置合わせに依存します。リンカーはキャッシュ行よりも高い精度による命令位置合わせを強制的に行うので、関連性がないと思える関数を変更すると、異なる命令の位置合わせが生じる可能性があります。位置合わせが異なると、パフォーマンスの向上や劣化が発生することがあります。

次の例では、同じ関数をわずかに異なる状況でコンパイルしてリンクしています。2 つの出力例は、er_print ユーティリティーからの注釈付き逆アセンブリリストを示しています。 2 つの例の命令は同じですが、位置合わせが異なっています。

この例の命令位置合わせでは、cmpbl,a の 2 つの命令を別々のキャッシュ行にマップし、この 2 つの命令の実行待ちに多大な時間が消費されます。


   Excl.     Incl.
User CPU  User CPU
    sec.      sec.
                             1. static int
                             2. ifunc()
                             3. {
                             4.     int i;
                             5.
                             6.     for (i=0; i<10000; i++)
                                <関数: ifunc>
   0.010     0.010              [ 6]    1066c:  clr         %o0
   0.        0.                 [ 6]    10670:  sethi       %hi(0x2400), %o5
   0.        0.                 [ 6]    10674:  inc         784, %o5
                             7.         i++;
   0.        0.                 [ 7]    10678:  inc         2, %o0
## 1.360     1.360              [ 7]    1067c:  cmp         %o0, %o5
## 1.510     1.510              [ 7]    10680:  bl,a        0x1067c
   0.        0.                 [ 7]    10684:  inc         2, %o0
   0.        0.                 [ 7]    10688:  retl
   0.        0.                 [ 7]    1068c:  nop
                             8.     return i;
                             9. }

この例の命令位置合わせでは、cmpbl,a の 2 つの命令を 1 つのキャッシュ行にマップし、この 2 つの命令の内 1 つの命令のみの実行待ちに多大な時間が消費されます。


   Excl.     Incl.
User CPU  User CPU
    sec.      sec.
                             1. static int
                             2. ifunc()
                             3. {
                             4.     int i;
                             5.
                             6.     for (i=0; i<10000; i++)
                                <関数: ifunc>
   0.        0.                 [ 6]    10684:  clr         %o0
   0.        0.                 [ 6]    10688:  sethi       %hi(0x2400), %o5
   0.        0.                 [ 6]    1068c:  inc         784, %o5
                             7.         i++;
   0.        0.                 [ 7]    10690:  inc         2, %o0
## 1.440     1.440              [ 7]    10694:  cmp         %o0, %o5
   0.        0.                 [ 7]    10698:  bl,a        0x10694
   0.        0.                 [ 7]    1069c:  inc         2, %o0
   0.        0.                 [ 7]    106a0:  retl
   0.        0.                 [ 7]    106a4:  nop
                             8.     return i;
                             9. }

命令発行遅延

特定のリーフ PC の示す命令の発行前に遅延があると、そのリーフ PC の出現回数が多くなることがあります。 これは、次のケースを初めとして、さまざまな理由で起きる可能性があります。

ハードウェアカウンタオーバーフローの関連付け

TLB ミスは別として、オーバーフローで生成された割り込みの処理に時間を要するなどのいくつかの理由から、ハードウェアカウンタのオーバーフローイベントの呼び出しスタックは、オーバーフローの発生時点ではなく、命令シーケンスの後ろの方で記録されます。 サイクルおよび発行された命令などの一部のカウンタの場合、この遅延は問題になりません。しかし、キャッシュミスや浮動小数点演算をカウントするようなカウンタの場合は、そのオーバーフローの原因となっているものとは別の命令がメトリックの原因とされます。イベントを引き起こした PC は記録された PC の前の数少ない命令だけであり、命令は逆アセンブリリスト内に正しく配置できます。ただし、この命令範囲内に分岐先がある場合、イベントを引き起こした PC に対 応する命令を見分けることは難しいか、または不可能です。メモリーアクセスイベントをカウントするハードウェアカウンタについて、コレクタはカウンタ名の前に「+」が付いている場合、イベントを引き起こした PC を検索します。

「ソース」タブ、「逆アセンブリ」タブ、「PC」タブの特別な行

アウトライン関数

フィードバック最適化コンパイルで、アウトライン関数が作成されることがあります。これらは、「ソース」タブと「逆アセンブリ」タブで、特別なインデックス行として表示されます。「ソース」タブでは、注釈は、アウトライン関数に変換されたコードブロックに表示されます。


                   Function binsearchmod inlined from source file ptralias2.c into the 
0.       0 .         58.         if( binsearchmod( asize, &element ) ) {
0.240    0.240       59.             if( key != (element << 1) ) {
0.       0.          60.                 error |= BINSEARCHMODPOSTESTFAILED;
                        <関数: main -- 行 60 [_$o1B60.main] からのアウトラインコード>
0.040    0.040     [ 61]                 break;
0.       0.          62.             }
0.       0.          63.         }

「逆アセンブリ」タブでは、アウトライン関数は通常、ファイルの末尾に表示されます。


                        <関数: main -- 行 85 [_$o1D85.main] からのアウトラインコード>
0.       0.             [ 85] 100001034:  sethi       %hi(0x100000), %i5
0.       0.             [ 86] 100001038:  bset        4, %i3
0.       0.             [ 85] 10000103c:  or          %i5, 1, %l7
0.       0.             [ 85] 100001040:  sllx        %l7, 12, %l5
0.       0.             [ 85] 100001044:  call        printf ! 0x100101300
0.       0.             [ 85] 100001048:  add         %l5, 336, %o0
0.       0.             [ 90] 10000104c:  cmp         %i3, 0
0.       0.             [ 20] 100001050:  ba,a        0x1000010b4
                        <関数: main -- 行 46 [_$o1A46.main] からのアウトラインコード>
0.       0.             [ 46] 100001054:  mov         1, %i3
0.       0.             [ 47] 100001058:  ba          0x100001090
0.       0.             [ 56] 10000105c:  clr         [%i2]
                        <関数: main -- 行 60 [_$o1B60.main] からのアウトラインコード>
0.       0.             [ 60] 100001060:  bset        2, %i3
0.       0.             [ 61] 100001064:  ba          0x10000109c
0.       0.             [ 74] 100001068:  mov         1, %o3

アウトライン関数の名前は角括弧内に表示され、コードの取り出し元関数の名前や、ソースコード内のセクションの先頭の行番号を含む、アウトライン化したコードのセクションに関する情報をエンコードします。これらの符号化された名前は、リリースごとに異なります。アナライザは、読みやすい関数名を表示します。詳細は、「アウトライン関数」を参照してください。

アプリケーションのパフォーマンスデータの収集中にアウトライン関数が呼び出されると、アナライザは注釈付き逆アセンブリに特別な行を表示して、その関数の包括的メトリックを示します。詳細は、「包括的メトリック」を参照してください。

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

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

コンパイラは、並列構造の種類、構造の取り出し元関数の名前、元のソースにおける構造の先頭の行番号、並列構造のシーケンス番号などをエンコードする本体関数に、符号化名を割り当てます。これらの符号化された名前は、マイクロタスクライブラリのリリースごとに異なりますが、より分かりやすい名前に復号化されて表示されます。

次に、関数リストに表示されるような一般的なコンパイラ生成の本体関数を示します。


7.415      14.860      psec_ -- 行 9 [_$s1A9.psec_] からの OMP 領域
3.873       3.903      craydo_ -- 行 10 [_$d1A10.craydo_] からの MP doall

この例で分かるように、構造が抽出された関数の名前が最初に示され、次に並列構造の種類、並列構造の行番号、コンパイラ生成の本体関数の符号化名が角括弧に表示されます。同様に、逆アセンブリコードには、特別なインデックス行が生成されます。


0.       0.            <関数: psec_ -- 行 9 [_$s1A9.psec_] からの OMP 領域>
0.       7.445         [24]    1d8cc:  save        %sp, -168, %sp
0.       0.            [24]    1d8d0:  ld          [%i0], %g1
0.       0.            [24]    1d8d4:  tst         %i1

0.       0.            <関数: craydo_ -- 行 10 [_$d1A10.craydo_] からの OMP doall>
0.       0.030         [ ?]    197e8:  save        %sp, -128, %sp
0.       0.            [ ?]    197ec:  ld          [%i0 + 20], %i5
0.       0.            [ ?]    197f0:  st          %i1, [%sp + 112]
0.       0.            [ ?]    197f4:  ld          [%i5], %i3

Cray の指令では、関数はソースコード行番号に関連付けされていない可能性があります。このような場合は、行番号の代わりに [ ?] が表示されます。注釈付きソースコードにインデックス行が表示される場合は、次のようにインデックス行は行番号なしで命令を示します。


                     9. c$mic  doall shared(a,b,c,n) private(i,j,k)
                  
                   以下のループは 23 行目のループと融合しました
                   以下のループは自動並列化処理が有効でないため並列化されていません
                   以下のループは自動並列化されました
                   以下のループは 12 行目のループと交換されました
                   以下のループは 12 行目のループと交換されました
3.873     3.903         <関数: craydo_ -- 行 10 [_$d1A10.craydo_] からの MP doall,
                      行番号なしの命令>
0.        3.903     10.            do i = 2, n-1

注 –

インデックス行やコンパイラのコメント行は、実際の表示では折り返されません。


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

動的にコンパイルされる関数は、プログラムの実行中にコンパイルされてリンクされる関数です。コレクタ API 関数 collector_func_load() を使用して必要な情報をユーザーが提供しないかぎり、コレクタは C や C++ で記述された動的にコンパイルされる関数に関する情報を持っていません。「関数」タブ、「ソース」タブ、「逆アセンブリ」タブに表示される情報は、次のように、collector_func_load() に渡される情報によって異なります。

コレクタ API 関数の詳細については、「動的な関数とモジュール」を参照してください。

Java プログラムでは、ほとんどのメソッドが JVM ソフトウェアによってインタプリタされます。別個のスレッドで動作する Java HotSpot 仮想マシンは、インタプリタの実行中にパフォーマンスを監視します。監視プロセス中仮想マシンは、1 つ以上のインタプリタを行なっているメソッドを取り出し、それらのメソッド用のマシンコードを生成し、元のマシンコードをインタプリタするのではなくさらに効率の良いマシンコードバージョンを実行することを決定する場合があります。

次の例に示すように、Java プログラムの場合は、コレクタ API 関数を使用する必要はありません。アナライザは、メソッドのインデックス行の下の特別な行を使って、注釈付き逆アセンブリリストでの Java HotSpot がコンパイルしたコードの存在を示します。


                     11.    public int add_int () {
                     12.       int       x = 0;
                         <関数: Routine.add_int()>
2.832     2.832          Routine.add_int() <HotSpot コンパイル済みリーフ命令>
0.        0.             [ 12] 00000000: iconst_0
0.        0.             [ 12] 00000001: istore_1

逆アセンブリリストには、コンパイルされた命令ではなく、インタプリタされたバイトコードのみが示されます。デフォルトでは、コンパイルされたコードのメトリックは、特別な行の隣りに表示されます。排他的および包括的 CPU 時間は、インタプリタされたバイトコードの各行に示されているすべての包括的および排他的 CPU 時間の合計とは異なります。通常は、何回かメソッドが呼び出されると、コンパイルされた命令の CPU 時間は、インタプリタされたバイトコードの CPU 時間の合計より多くなります。なぜなら、インタプリタされたコードは、メソッドが最初に呼び出されたときに一度だけ実行されるのに対し、コンパイルされたコードはその後も実行されるからです。

注釈付きソースには、Java HotSpot でコンパイルされた関数は表示されません。その代わりに、行番号なしで命令を示す特別なインデックス行が表示されます。たとえば、前述の逆アセンブリの抜粋に対応する注釈付きソースは、次のようになります。


                     11.    public int add_int () {
2.832     2.832        <関数: Routine.add_int(), 行番号なしの命令>
0.        0.         12.       int       x = 0;
                       <関数: Routine.add_int()>

Java ネイティブ関数

ネイティブコードは、Java コードにより Java Native Interface (JNI) を介して呼び出される、元は C、C++、または Fortran で記述されたコンパイル済みコードです。次の例は、デモプログラム jsynprog に関連付けられたファイル jsynprog.java の注釈付き逆アセンブリからの抜粋です。


                     5. class jsynprog
                        <関数: jsynprog.<init>()>
0.       5.504          jsynprog.JavaCC() <Java ネイティブメソッド>
0.       1.431          jsynprog.JavaCJava(int) <Java ネイティブメソッド>
0.       5.684          jsynprog.JavaJavaC(int) <Java ネイティブメソッド>
0.       0.             [  5] 00000000: aload_0
0.       0.             [  5] 00000001: invokespecial <init>()
0.       0.             [  5] 00000004: return

ネイティブメソッドは Java ソースに含まれていないため、jsynprog.java の注釈付きソースの先頭には、行番号なしで命令を示す特別なインデックス行を使って各 Java ネイティブメソッドが表示されます。


0.       5.504          <関数: jsynprog.JavaCC(), 行番号なしの命令>
0.       1.431          <関数: jsynprog.JavaCJava(int), 行番号なしの命令>
0.       5.684          <関数: jsynprog.JavaJavaC(int), 行番号なしの命令>

注 –

実際の注釈付きソースの表示では、インデックス行は折り返されません。


クローン生成関数

コンパイラは、通常以上の最適化が可能な関数への呼び出しを見分けることができます。このような呼び出しの一例として、渡される引数の一部が定数である関数への呼び出しがあります。コンパイラは、最適化できる特定の呼び出しを見つけると、クローンと呼ばれるこの関数のコピーを作成して、最適化コードを生成します。

注釈付きソースでは、コンパイラのコメントは、クローン生成関数が作成されたかどうかを示します。


0.       0.       ソースファイル clone.c の関数 foo がクローン関数 _$c1A.foo を
                        作成しました。定義パラメータはクローンに伝達されました
0.       0.570     27.    foo(100, 50, a, a+50, b);

注 –

実際の注釈付きソースの表示では、コンパイラのコメント行は折り返されません。


クローン関数名は、特定の呼び出しを識別する、符号化された名前です。前述の例では、コンパイラのコメントは、クローン生成関数の名前が _$c1A.foo であることを示しています。次に示すように、この関数は関数リストに表示されます。


0.350     0.550     foo
0.340     0.570     _$c1A.foo

クローン生成関数はそれぞれ別の命令セットを持っているので、注釈付き逆アセンブリリストには、クローン生成関数が別々に表示されます。これらはソースファイルに関連付けられていないため、命令はいずれのソース行番号とも関連付けられていません。次に、クローン生成関数の注釈付き逆アセンブリの最初の数行を示します。


0.       0.           <関数: _$c1A.foo>
0.       0.           [?]    10e98:  save        %sp, -120, %sp
0.       0.           [?]    10e9c:  sethi       %hi(0x10c00), %i4
0.       0.           [?]    10ea0:  mov         100, %i3
0.       0.           [?]    10ea4:  st          %i3, [%i0]
0.       0.           [?]    10ea8:  ldd         [%i4 + 640], %f8

静的関数

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


0.       0.       <static>@0x18780
0.       0.       <static>@0x20cc
0.       0.       <static>@0xc9f0
0.       0.       <static>@0xd1d8
0.       0.       <static>@0xe204

「PC」タブでは、前述の関数は、次のようにオフセットとともに示されます。


0.       0.       <static>@0x18780 + 0x00000818
0.       0.       <static>@0x20cc + 0x0000032C
0.       0.       <static>@0xc9f0 + 0x00000060
0.       0.       <static>@0xd1d8 + 0x00000040
0.       0.       <static>@0xe204 + 0x00000170

ストリップ済みライブラリ内で呼び出された関数は、「PC」タブで次のように表示される場合もあります。


<library.so> -- 関数が見つかりません + 0x0000F870

包括的メトリック

注釈付き逆アセンブリでは、アウトライン関数が要した時間にタグを付けるための特別な行が存在します。

次に、アウトライン関数が呼び出されたときに表示される注釈付き逆アセンブリの例を示します。


0.       0.        43.         else
0.       0.        44.         {
0.       0.        45.                 printf("else reached\n");
0.       2.522         <アウトライン関数の包括的メトリック>

分岐先

注釈付き逆アセンブリリストに示される擬似行の <branch target> (分岐先) は、バックトラッキングアルゴリズムが分岐先内で実行するため、その有効アドレスを見つけるためのバックトラッキングに失敗した命令の PC に対応します。

実験なしのソース/逆アセンブリの表示

実験を実行しなくても、er_src ユーティリティーを使用して、注釈付きソースコードや注釈付き逆アセンブリコードを表示できます。メトリックが表示されないことを除けば、この表示は、アナライザで生成されるものと同じです。er_src コマンドの構文は次のとおりです。


er_src [ -func | -{source,src} item tag | -disasm item tag |
-{cc,scc,dcc} com_spec | -outfile filename | -V ] object

object は、実行可能ファイル、共有オブジェクト、またはオブジェクトファイル (.o ファイル) の名前です。

item は、関数名、または実行可能オブジェクトや共有オブジェクトの構築に使用されたソースファイルまたはオブジェクトファイルの名前です。item は、functionfile’ の形式でも指定できます。この場合、er_src は指定されたファイルのソースコンテキストに、指定された関数のソースまたは逆アセンブリを表示します。

tag は、同じ名前の関数が複数存在する場合に、参照する item を決定するために使用されるインデックスです。これは必須ですが、関数の解決に不要な場合は無視されます。

特別な item および tag の all -1 は、オブジェクトのすべての関数に対して、注釈付きソースまたは逆アセンブリを生成するように er_src に指示します。


注 –

実行可能ファイルや共有オブジェクトに all -1 を使用した結果生成される出力は、非常に大きくなることもあります。


次に、er_src ユーティリティーに使用可能なオプションについて説明します。

-func

指定オブジェクトのすべての関数を一覧表示します。

-{source,src} item tag

リストされた item の注釈付きソースを示します。

-disasm item tag

リストに逆アセンブリを含めます。デフォルトでは、リストに逆アセンブリは含まれません。ソースがない場合は、コンパイラのコメントなしで逆アセンブリのリストが生成されます。

-{c,scc,dcc} com-spec

表示するコンパイラのコメントクラスを指定します。com-spec は、コロンで区切られたクラスのリストです。com-spec は、-scc オプションが使用されている場合はソースのコンパイラのコメントに、-dcc オプションが使用されている場合は逆アセンブリのコメントに、-c が使用されている場合はソースと逆アセンブリのコメントの両方に適用されます。これらのクラスについては、 「ソースリストと逆アセンブリリストを管理するコマンド」を参照してください。

コメントクラスは、デフォルト値ファイルで指定することができます。システム全体の er.rc デフォルト値ファイルが最初に読み取られ、次にユーザーのホームディレクトリの .er.rc ファイルが、存在する場合読み取られます。そして現在のディレクトリの .er.rc ファイルが読み取られます。ホームディレクトリの .er.rc ファイル内のデフォルト値はシステムのデフォルト値よりも優先し、現在のディレクトリの .er.rc ファイル内のデフォルト値は、ユーザーのホームおよびシステムのデフォルト値よりも優先します。これらのファイルは、アナライザと er_print ユーティリティーによっても使用されますが、er_src ユーティリティーが使用するのは、ソースおよび逆アセンブリのコンパイラのコメントに関する設定の部分だけです。デフォルト値ファイルの詳細については、「デフォルト値を設定するコマンド」を参照してください。er_src ユーティリティーは、デフォルト値ファイル内の scc および dcc 以外のコマンドを無視します。

-outfile filename

リストの出力用ファイル filename を開きます。デフォルトの場合、またはファイル名がダッシュ (-) の場合は、出力は stdout に書き込まれます。

-V

現在のリリースバージョンを表示します。