適切なコンパイラオプションを選択することは、パフォーマンスを上げるための第一歩です。Sun コンパイラは、オブジェクトコードに影響する幅広いオプションを提供します。デフォルトの (コンパイルコマンド行にオプションを何も明示的に指定しない) 場合、ほとんどのオプションはオフです。パフォーマンスを上げるには、これらのオプションを明示的に選択しなければいけません。
パフォーマンスオプションは通常デフォルトではオフです。 なぜなら、ほとんどの最適化によって、コンパイラは、ユーザーのソースコードについて仮定を行うからです。標準のコーディング技術に準拠し、潜在的な副作用を発生させないプログラムは、正しく最適化できるはずです。しかし、標準の技術を恣意的に扱うプログラムは、コンパイラの仮定のいくつかと衝突する可能性があります。この結果作成されるコードは高速に実行するかもしれませんが、計算の結果は間違っている可能性があります。
推奨できる方法は、まず、すべてのオプションをオフにしてコンパイルし、計算の結果が正確であることを検証し、これらの最初の結果とパフォーマンスプロファイルをベースラインとして使用する方法です。それから、実行結果とパフォーマンスをベースラインと比較しながら、段階的に、オプションを追加してコンパイルし直します。数値結果が変わるようであれば、そのプログラムには疑わしいコードがあるといえます。その問題がどこにあるのかを注意深く解析して、プログラムし直す必要があります。
最適化オプションを追加した結果、パフォーマンスがあまり上がらない (あるいは下がってしまった) 場合、そのコーディングにはコンパイラがパフォーマンスを上げる余地がないのかもしれません。次の段階は、プログラムをソースコードレベルで解析し、構造を変更することによって、パフォーマンスを上げることです。
次の表にリストしたコンパイラオプションによって、デフォルトのコンパイルで作成されるプログラムのパフォーマンスを上げるための方法のレパートリーは広がります。このリストには、コンパイラの中でもよりパフォーマンスに影響を与えるオプションだけを紹介しました。完全なリストについては、『Fortran ユーザーズガイド』を参照してください。
表 9–1 パフォーマンスに影響を与えるオプション
処理 |
オプション |
---|---|
さまざまな最適化オプションをいっしょに使用する |
-fast |
コンパイラの最適化レベルを n に設定する |
-On (-O = -O3) |
ターゲットハードウェアを指定する |
-xtarget=sys |
特定の命令セットアーキテクチャーを指定する |
-xarch=isa |
パフォーマンスプロファイルデータを使用して最適化する (-O5 で) |
-xprofile=use |
ループを n まで展開する |
-unroll=n |
浮動小数点の簡約化と最適化を許可する |
-fsimple=1|2 |
依存関係の解析を行い、ループを最適化する |
-depend |
内部手続きの最適化を実行する |
-xipo |
このようなオプションはコンパイル時間を増やすものもあります。なぜなら、プログラムをより深く解析するからです。オプションの中には、呼び出すルーチンと呼び出されるルーチンを同じファイルに集めておくと (それぞれを別々なファイルに入れておくよりも) うまく動作するものもあります。これによって、解析が大域的に行われるからです。
このオプション 1 つで、いくつものパフォーマンスオプションを選択したことになります。
このオプションは、リリースごと、またはコンパイラごとに変更されることのあるほかのオプションを選択する機能として定義されています。-fast により選択されるいくつかのオプションはすべてのプラットフォームで使用できない可能性があります。-fast の展開を表示するには、-dryrun フラグを使用してコンパイルしてください。
-fast により選択されるいくつかのオプションはすべてのプラットフォームで使用できない可能性があります。 しかし、オプションによっては、アプリケーションで使用できない場合があります。-fast を使用して、最大のパフォーマンスを得るためにアプリケーションをコンパイルしてください。しかし、さらに調整が必要な場合があります。-fast を指定してコンパイルしたプログラムが正しく動作しない場合、-fast を形成している個々のオプションを調査して、プログラムを正しく動作させるオプションだけを呼び出してください。
また、-fast でコンパイルされたプログラムは、使用するデータセットにより、高いパフォーマンスと正確な結果を実現できないことがあります。浮動小数点演算の特定プロパティーに依存しているプログラムは、-fast を使用してコンパイルしないでください。
-fast で選択されたオプションの一部は暗黙的にリンクするため、コンパイルとリンクを別々に行う場合は、リンク時も必ず -fast を使用してください。
-fast では次のオプションが選択されます。
-dalign
-depend
-fns
-fsimple=2
-ftrap=common
-fround=nearest (Solaris のみ)
-libmil
-xtarget=native
-O5
-xlibmopt (Solaris のみ)
-pad=local (SPARC のみ)
-xvector=lib (SPARC のみ)
-nofstore (x86 のみ)
-xregs=frameptr (x86 のみ)
-fast は、コンパイラの最適化能力のほとんどを簡単に引き出すための方法です。複合オプションは個別にも指定できます。また、それぞれに注意すべき副作用があります (『Fortran ユーザーズガイド』を参照)。-fast の実際の展開内容は、コンパイラのリリースが変わるたびに変更される可能性があることに注意してください。-dryrun を付けてコンパイルすると、すべてのコマンド行フラグの展開内容を見ることができます。
-fast のあとに別のオプションを追加して、さらに最適化を指定できます。たとえば、次のようにします。
f95 -fast -m64 ...
64 ビット対応のプラットフォーム向けにコンパイルします。
-fast には、-dalign、-fns、-fsimple=2 が含まれます。このため、-fast を指定してプログラムをコンパイルすると、結果として、非標準の浮動小数点演算、非標準のデータ配列、式評価の非標準の順序になる可能性があります。これらの選択オプションは、ほとんどのプログラムに適していない可能性があります。
-O オプションを明示的に、あるいは、-fast などのマクロオプションで暗黙的に指定しないかぎり、コンパイラは最適化を行いません。ほとんどすべての場合、コンパイル時に最適化レベルを指定すると、プログラムの実行パフォーマンスは上がります。一方、最適化レベルを上げるほど、コンパイル時間が増え、コードのサイズも大きくなる可能性があります。
ほとんどの場合、パフォーマンス、コードのサイズ、コンパイル時間をもっともバランスよくコンパイルするのはレベル -O3 です。 -O4 は、呼び出し側と同じソースファイルに入っているルーチンの呼び出しの自動インライン化を追加します。副プログラム呼び出しのインライン化の詳細については、『Fortran ユーザーズガイド』を参照してください。
レベル -O5 は、低いレベルには適用できない、さらに積極的な最適化テクニックを追加します。一般的に、-O3 より上のレベルは、プログラム中でもっとも計算が多い、つまりパフォーマンスが上がる見込みが大きい部分のルーチンだけに指定するものです。ちなみに、異なる最適化レベルでコンパイルしたプログラムをいっしょにリンクしても何の問題もありません。
C$ PRAGMA SUN OPT=n 指令を使用して、ソースファイルのルーチンごとに異なる最適化レベルを設定します。この指令はコンパイラのコマンド行の -On フラグに優先しますが、-xmaxopt=n フラグで最大最適化レベルを設定して使用しなければいけません。 詳細は、f95(1) のマニュアルページを参照してください。
-xprofile=use と組み合わせた場合、コンパイラはレベル -O3 以上の最適化をより効率的に適用します。このオプションを使用すると、オプティマイザは、-xprofile=collect でコンパイルしたプログラムが典型的な入力データを使用して生成した実行時実行プロファイルから指示を受けます。フィードバックプロファイルは、どこで最適化が最大の効果を発揮するかをコンパイラに示します。これは特に -O5 で重要になります。次に示す例は、より高い最適化レベルでプロファイルを収集する典型的な例です。
demo% f95 -o prg -fast -xprofile=collect prg.f ... demo% prg demo% f95 -o prgx -fast -O5 -xprofile=use:prg.profile prg.f ... demo% prgx |
前述の例の最初のコンパイルで、実行時に文カバレージ統計を生成する実行可能ファイルが生成されます。2 回目のコンパイルで、このパフォーマンスデータを使用して、プログラムを最適化しています。
-xprofile オプションに関する詳細は、『Fortran ユーザーズガイド』を参照してください。
-dalign を使用すると、コンパイラはダブルワードのロード命令またはストア命令を (可能であれば) 生成できます。 データの移動量が多いプログラムは、このオプションを付けてコンパイルすれば、その恩恵を十分に受けることができます。-dalign は、-fast によって選択されるオプションの 1 つです。ダブルワード命令の速度は、同等のシングルワード命令と比べると、ほとんど倍になります。
しかし、-dalign を使用するときは (したがって、-fast を使用するときも) 十分注意しなければいけません。なぜなら、COMMON ブロック中のデータの特定の境界合わせを予想してコーディングされたプログラムのうち、問題を起こすものがあるからです。-dalign を使用すると、コンパイラはパディングを追加して、倍精度と 4 倍精度のデータをすべて (REAL も COMPLEX も) ダブルワード境界にそろえようとします。その結果、次のようなことが起こります。
パディングを追加したために、COMMON ブロックが予想よりも大きくなることがあります。
COMMON を共有するプログラム単位のいずれか 1 つでも -dalign を付けてコンパイルした場合、すべての単位を -dalign を付けてコンパイルしなければいけません。
たとえば、複数のデータ型が混在する COMMON ブロック全体を 1 つの配列として別名付けを行うことによって、データを書き込むプログラムは -dalign を付けるとうまく動作しません。なぜなら、倍精度変数や 4 倍精度変数のパディングのために、プログラムが予想するよりもブロックが大きくなるからです。
最適化レベル -O3 以上に -depend を追加すると、DO ループとループの入れ子に関するコンパイラの最適化能力が拡張されます。このオプションを使用すると、オプティマイザは反復間のデータの依存関係を解析し、そのループ構造を変形できるかどうか決定します。データの依存関係のないループだけがその構造を変形できます。しかし、この解析を追加すると、コンパイル時間が増えます。
指示しないかぎり、コンパイラは浮動小数点計算を簡易化しようとしません (デフォルトは -fsimple=0)。-fsimple=2 を追加すると、オプティマイザはさらに簡易化を行うことができます。 しかし、簡易化を行うと、丸めの影響によって、結果がわずかに違うという問題が発生する可能性があります。-fsimple レベル 1 か 2 を使用する場合は、すべてのプログラム単位を同じようにコンパイルし、数値精度の整合性が失われないようにする必要があります。このオプションについての重要な情報は、『Fortran ユーザーズガイド』を参照してください。
長い繰り返しを持つ短いループを展開すると、いくつかのルーチンはその恩恵を受けることがあります。しかし、展開はプログラムのサイズを増やすことにもなり、ほかのループのパフォーマンスを下げることにもなります。 n=1 を使用すると (デフォルト)、オプティマイザは自動的にループを展開しません。n が 1 より大きいときは、オプティマイザは、深さが n までループを展開しようとします。
コンパイラのコードジェネレータはループの展開をさまざまな要因に応じて決定します。コンパイラは、オプションが n>1 で指定されている場合でもループを展開しないことがあります。
繰り返しが可変の DO ループを展開する場合、展開したループとオリジナルのループの両方がコンパイルされます。繰り返し数を実行時にテストして、展開したループを実行するのが適切かどうかを決定します。ループを展開すると、特に文が 1 つか 2 つしかないループの場合は、反復ごとに行われる計算量が増えるので、オプティマイザがレジスタをスケジュールし演算を単純化する機会が増えます。繰り返しの数、ループの複雑さ、展開の深さの選択のかね合いは簡単に決定できず、ある程度の経験が必要となるでしょう。
次に示す例は、-unroll=4 を指定して、簡単なループを深さが 4 まで展開する様子を示しています (このオプションを使用しても、ソースコードは変更されません)。
元のループ :
DO I=1,20000 X(I) = X(I) + Y(I)*A(I) END DO |
深さ 4 まで展開すると次のコーディングと同じようになります:
DO I=1, 19997,4 TEMP1 = X(I) + Y(I)*A(I) TEMP2 = X(I+1) + Y(I+1)*A(I+1) TEMP3 = X(I+2) + Y(I+2)*A(I+2) X(I+3) = X(I+3) + Y(I+3)*A(I+3) X(I) = TEMP1 X(I+1) = TEMP2 X(I+2) = TEMP3 END DO |
この例は、固定した繰り返しの簡単なループを示しています。可変の繰り返し数を持つループに対しては、構造の変更はもっと複雑になります。
コンパイラにターゲットのコンピュータハードウェアの正確な情報を伝えると、パフォーマンスが上がるプログラムもあります。プログラムのパフォーマンスを重視する場合は、ハードウェアを適切に指定することが極めて重要です。特に、プログラムをより新しい SPARC システム上で実行する場合には重要になります。しかし、ほとんどのプログラムと古い SPARC プロセッサの場合、パフォーマンスはそれほど上がらず、汎用指定だけで十分です。
『Fortran ユーザーズガイド』には、-xtarget= が認識するすべてのシステム名がリストされています。特定のシステム名に対して (たとえば、UltraSPARC-II なら ultra2)、-xtarget は、システムに適切に一致するように、-xarch、-xcache、-xchip の組み合わせに展開されます。オプティマイザはこれらの指定を使用して、従うべき方法と生成する命令を決定します。
-xtarget=native は特別な設定で、これを指定すると、オプティマイザはホストシステム (コンパイルを行うシステム) をターゲットとしてコードをコンパイルします。コンパイルと実行を同じシステム上で行うときは、このオプションが断然便利です。実行システムが不明であるときは、汎用のアーキテクチャー用にコンパイルするのが望ましい方法です。そのため、最適のパフォーマンスを得ることはできませんが、-xtarget=generic がデフォルトになります。
-xtarget フラグと -xchip フラグは、どちらも ultra3 と ultra3 バリアントを受け入れ、UltraSPARC-III プロセッサおよび UltraSPARC-IV プロセッサ用に最適化されたコードを生成します。最新の UltraSPARC プラットフォーム上でアプリケーションのコンパイルと実行を行うときは、-fast フラグを指定すると、そのプラットフォームに適したコンパイラ最適化オプションを自動的に選択できます。
クロスコンパイル (最新の UltraSPARC プラットフォーム以外のプラットフォームでコンパイルするが、UltraSPARC-III プロセッサで実行するためのバイナリを生成する) には、次のフラグを使用してください。
-fast -xtarget=ultra3
64 ビットコード生成用にコンパイルするには -m64 を使用してください。
最新の UltraSPARC プロセッサ用の -xtarget フラグのリストについては、『Fortran ユーザーズガイド』を参照してください。
-xprofile=collect: および -xprofile=use: を使用したパフォーマンスプロファイルは、特に UltraSPARC-III プラットフォームおよび UltraSPARC-IV プラットフォームで有効です。これは、コンパイラがもっとも頻繁に実行されるプログラムのセクションを特定し、局所的な最適化を実行して、最高のパフォーマンスを引き出すことができるからです。
Sun Studio Fortran コンパイラは、Solaris および Linux x86 プラットフォーム向けに 32 ビットおよび 64 ビット両方のコードのコンパイルをサポートしています。
-xtarget=pentium3 フラグは、次のように展開されます。-xarch=sse -xchip=pentium3 -xcache=16/32/4:256/32/4
Pentium 4 システムの場合、-xtarget=pentium4 は次のように展開されます。-xarch=sse2 -xchip=pentium4 -xcache=8/64/4:256/128/8
新しい -m64 オプションは、64 ビット x64 命令セット用のコンパイルを指定します。
新しい -xtarget オプションの -xtarget=opteron は、32 ビット AMD コンパイル用の -xarch、-xchip、-xcache の設定を指示します。
64 ビットコードを生成するには、コマンド行で -fast および -xtarget のあとに -m64 を指定する必要があります。-xtarget オプションは、自動的には 64 ビットコードを生成しません。-xtarget 値の定義もしているマクロのため、-fast オプションもまた 32 ビットコードになります。-xtarget=native64 および -xtarget=generic64 を除く、現在の -xtarget 値はすべて 32 ビットコードになるため、64 ビットコードを生成するには、次に示すように、コマンド行で -fast または -xtarget のあとに -xarch=m64 を指定する必要があります。
% f95 -fast -m64 または % f95 -xtarget=opteron -m64
-xarch=amd64 が指定されると、コンパイラが __amd64 および __x86_64 を事前定義するようになりました。
32 ビットおよび 64 ビット x86 プラットフォームでのコンパイルとパフォーマンスについての詳細は、『Fortran ユーザーズガイド』を参照してください。
この新しい f95 コンパイラフラグは、Forte Developer 6 update 2 リリースで導入されたもので、内部手続き解析パスを呼び出して、プログラム全体の最適化を実行します。-xcrossfile と異なり、-xipo はリンクステップですべてのオブジェクトファイルを最適化し、コンパイルコマンドのソースファイルだけに限定されません。
-xipo は大規模な複数ファイルにわたるアプリケーションをコンパイルおよびリンクする際に特に有効です。-xipo でコンパイルされたオブジェクトファイルは、その中に保存された解析情報を持っています。これにより、ソースおよびコンパイル済みプログラムファイルの内部手続き解析ができるようになります。
内部手続き解析を効果的に使用する方法について詳細は、『Fortran ユーザーズガイド』を参照してください。
ソースコード内の重要なポイントに ASSUME 指令を追加すると、検出しにくいプログラムの重要情報が明らかになり、コンパイラへの最適化のための支持として役立ちます。たとえば、DO ループのトリップカウントが常にある値より大きいことや、IF 分岐が行われない可能性が高いことを、コンパイラに知らせることができます。コンパイラはこの情報を利用して、これらの表明に基づくより最適化されたコードを生成できます。
そのうえ、プログラマは、表明が誤りであったことが実行時にわかった場合に警告メッセージが発行されるようにすることによって、ASSUME プラグマを使用してプログラムの実行を検証できます。
詳細は、『Fortran ユーザーズガイド』の第 2 章の ASSUME プラグマの説明と、第 3 章の -xassume_control コンパイラコマンド行オプションの説明を参照してください。
さまざまな最適化オプションを使用し、プログラムをコンパイルし、実際の実行時パフォーマンスを測定したと仮定します。次の段階は、Fortran ソースプログラムを調べて、さらにチューニングできるかどうかを決定します。
計算時間のほとんどを消費するプログラムの部分だけに注目し、次の方針を考えます。
手作業で作成した手続きを、最適化された同等のライブラリへの呼び出しに置き換える。
重要なループから入出力、呼び出し、不必要な条件操作を削除する。
最適化を抑制する可能性がある別名を削除する。
ブロック IF を使用して、複雑なコードを整理する。
前述したものは、パフォーマンスを上げる可能性を持つプログラミング技術の一例です。さらに、特定のハードウェア構成にあわせて手作業でソースコードを調整することもできます。しかし、このような作業はコードをわかりにくくするだけでなく、コンパイラのオプティマイザもパフォーマンスを上げにくくなります。手作業でソースコードをチューニングしすぎると、その手続きの本来の意図が隠され、異なるアーキテクチャー上ではパフォーマンスに重大な悪影響を与えかねません。
ほとんどの場合、商業用 (あるいはシェアウェア) の最適化されたライブラリは、ユーザーが手作業でコーディングしたものよりも、はるかに効率的に標準の計算手続きを実行します。
たとえば、Sun Performance LibraryTM は、標準の LAPACK、BLAS、FFTPACK、VFFTPACK、LINPACK ライブラリをベースとした数学サブルーチンで、高度に最適化されています。このライブラリのルーチンを使用すると、パフォーマンスは手作業でコーディングしたときよりも大幅に上がります。詳細は、『Sun Performance Library User's Guide』を参照してください。
Sun Studio パフォーマンスアナライザを使用して、プログラムの重要な計算部分を調べます。そして、注意深くループまたはループの入れ子を解析し、オプティマイザが最適なコードを生成するのを抑制している、つまりパフォーマンスを下げているコーディングを除去します。標準以外のコーディングが多いと、移植が困難になり、さらにはコンパイラによる最適化を抑制する可能性があります。
パフォーマンスを上げるためのプログラムの書き直しテクニックに関しては、この章の最後に紹介する、さまざまな参考文献で取り上げられています。ここでは 3 つの代表的なアプローチを説明します。
プログラムの重要な計算作業を囲んでいるループ、あるいはループの入れ子内の入出力は、パフォーマンスを大幅に下げる原因となります。入出力ライブラリで消費される CPU 時間は、そのループで消費される時間のほとんどを占めます。また、入出力はプロセス割り込みの原因ともなるので、プログラムスループットを下げます。可能なかぎり、入出力を計算ループの外に出すことで、入出力ライブラリへの呼び出し回数が大幅に減ります。
副プログラムがループの深い入れ子から呼び出されると、何千回と呼び出される可能性もあります。呼び出しごとの各ルーチン内で消費される時間は少なくても、その合計の影響はかなりのものです。また、副プログラムの呼び出しは、その呼び出しを含むループの最適化を抑制します。 なぜなら、コンパイラは、その呼び出しのレジスタの状態に関して仮定を行うことができないからです。
副プログラム呼び出しの自動インライン化 (-inline=x,y,..z、または -O4 を使用する) は、コンパイラが実際の呼び出しを副プログラム自身で置き換える (副プログラムをループの中に入れる) ための 1 つの方法です。インライン化されるべきルーチンの副プログラムのソースコードは、呼び出し側のルーチンと同じファイルに存在しなければいけません。
副プログラム呼び出しを削減する方法はほかにもあります。
文関数を使用する。呼び出される外部関数が単純な数学関数である場合、その関数を文関数 (あるいは文関数の集合) として書き直すことができます。文関数はコンパイル時にインライン化され、最適化できます。
ループを副プログラムに入れる。つまり、副プログラムを書き換えて、(ループの外で) 呼び出される回数を減らし、呼び出しごとに値のベクトルあるいは配列を操作するようにします。
計算が多いループ内の操作が複雑であると、コンパイラの最適化は抑制される可能性があります。一般的に、算術的な IF と論理的な IF をすべてブロック IF に置き換えるのがよい方法であるとされています。
元のコード: IF(A(I)-DELTA) 10,10,11 10 XA(I) = XB(I)*B(I,I) XY(I) = XA(I) - A(I) GOTO 13 11 XA(I) = Z(I) XY(I) = Z(I) IF(QZDATA.LT.0.) GOTO 12 ICNT = ICNT + 1 ROX(ICNT) = XA(I)-DELTA/2. 12 SUM = SUM + X(I) 13 SUM = SUM + XA(I) 整理されたコード: IF(A(I).LE.DELTA) THEN XA(I) = XB(I)*B(I,I) XY(I) = XA(I) - A(I) ELSE XA(I) = Z(I) XY(I) = Z(I) IF(QZDATA.GE.0.) THEN ICNT = ICNT + 1 ROX(ICNT) = XA(I)-DELTA/2. ENDIF SUM = SUM + X(I) ENDIF SUM = SUM + XA(I) |
ブロック IF を使用すると、コンパイラが最適なコードを生成する機会が多くなるだけでなく、読みやすくなるので、移植性も確保されます。
-g デバッグオプションを使用してコンパイルする場合、Sun Studio パフォーマンス解析ツールの一部である er_src(1) ユーティリティーを使用して、コンパイラにより生成されたソースコードの注釈を表示することができます。生成されたアセンブリ言語の注釈付きソースコードを表示することもできます。次に、er_src によって生成された、単純な do ループに関するコメントの例を示します。
demo% f95 -c -g -O4 do.f demo% er_src do.o Source file: /home/user21/do.f Object file: do.o Load Object: do.o 1. program do 2. common aa(100),bb(100) 関数 x は、ソースファイル do.f から次の行のコードにインライン化される 次のループは定常サイクル数 = 3 でスケジュールされました 次のループは 5 回、展開されました 次のループは繰り返しにつき 2 のロード、1 のストア、0 の先読み、1 の Fpadds、1 の Fpmuls、0 の Fpdivs を行います 3. call x(aa,bb,100) 4. end 5. subroutine x(a,b,n) 6. real a(n), b(n) 7. v = 5. 8. w = 10. 次のループは定常サイクル数 = 3 でスケジュールされました 次のループは 5 回、展開されました 次のループは繰り返しにつき 2 のロード、1 のストア、0 の先読み、1 の Fpadds、1 の Fpmuls、0 の Fpdivs を行います 9. do 1 i=1,n 10. 1 a(i) = a(i)+v*b(i) 11. return 12. end |
コメントのメッセージにより、コンパイラにより実行された最適化処理の詳細が分かります。この例では、サブルーチンの呼び出しをインライン化し、ループを 5 回展開しています。この情報を検証することで、将来の最適化戦略に役立てることができるでしょう。
コンパイラのコメントおよび逆アセンブルコードの詳細については、Sun Studio のマニュアル『 プログラムのパフォーマンス解析 』を参照してください。