実験の注釈付きソースコードは、パフォーマンスアナライザで「アナライザ」ウィンドウの左の区画にある「ソース」タブを選択することで表示できます。または、実験を実行しなくても、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 つの場所にまとめることによってパフォーマンスを向上できます。たとえば、コードブロックの if と else の分岐の両方で同じ演算が記述されている場合、コンパイラはその演算を 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-prefix と new-prefix を指定できます。ソースファイル、オブジェクトファイル、または共有オブジェクトのパス名が old-prefix で指定した接頭辞で始まる場合、古い接頭辞は new-prefix で指定した接頭辞に置き換えられます。結果のパスは、ファイルの検索に使用されます。複数の pathmap コマンドが提供されており、それぞれが、ファイルが見つかるまで試行されます。
コンパイル処理では、要求される最適化レベルに応じて多くの段階があり、変換によって命令とソース行のマッピングに混乱が生じることがあります。最適化によっては、ソース行の情報が完全に失われたり、混乱が生じたりすることがあります。コンパイラは、さまざまな発見手法によって命令のソース行を追跡しますが、こうした手法は絶対ではありません。
命令のメトリックについては、実行対象の命令を待っている間に発生したメトリックとして解釈する必要があります。イベントが記録されるときに実行中である命令がリーフ PC と同じソース行に存在している場合、メトリックはこのソース行を実行した結果であると解釈できます。ただし、実行中の命令とリーフ PC が存在しているソース行がそれぞれ異なる場合、リーフ PC が存在しているソース行のメトリックの少なくとも一部は、実行中命令のソース行が実行待ちしていた間に集計されたメトリックであると解釈する必要があります。この一例としては、1 つのソース行で計算された値が次のソース行で使用される場合が挙げられます。
メトリックの解釈方法がもっとも問題となるのは、キャッシュミスやリソース待ち行列ストールなど、実行が大幅に遅延している場合や、命令が直前の命令の結果を待っている場合です。このような場合、ソース行のメトリックが異常に高く見えることがあります。コード内のほかのソース行を調べて、こういった高メトリック値の原因である行を付き止めてください。
表 8–1 に、注釈付きソースコードの行に表示可能な 4 種類のメトリックをまとめました。
表 8–1 注釈付きソースコードのメトリック