一般的に、アプリケーションプログラムをあるシステムのコンパイラから別のシステムのコンパイラに移植するとき、非標準のコーティングを削除すれば、移植は簡単になります。あるシステムで成功した最適化や回避策が、ほかのシステムでは曖昧であり、コンパイラを混乱させることもあります。特に、特定のアーキテクチャー用に最適化された手作業によるチューニングは、ほかの場所ではパフォーマンスを低下させる原因となる可能性もあります。これについては、パフォーマンスとチューニングに関する章で説明します。しかし、次の話題は、移植に際して、一般的に考慮すべきことです。
局所変数や COMMON 変数を自動的にゼロに初期化するシステムもあれば、「非数値」(NaN) に初期化するシステムもあります。しかし、標準的な取り決めはありません。 したがって、プログラムは変数の初期値に関して仮定を行うべきではありません。移植性を最大限保証するために、プログラムはすべての変数を初期化すべきです。
別名参照は、同じ記憶領域アドレスが複数の名前で参照されるときに発生します。通常、これは、ポインタを使用している場合や、副プログラムへの実引数が、それら実引数間で、あるいは副プログラム内の COMMON 変数間でオーバーラップしている場合に起こります。たとえば、引数 X と Z は同じ記憶領域の位置を参照します。B と H も同様です。
COMMON /INS/B(100) REAL S(100), T(100) ... CALL SUB(S,T,S,B,100) ... SUBROUTINE SUB(X,Y,Z,H,N) REAL X(N),Y(N),Z(N),H(N) COMMON /INS/B(100) ... |
古い Fortran プログラムの多くは、このような別名での参照を、当時の Fortran 言語では利用できなかった、ある種の動的なメモリー管理の手段として利用していました。
別名での参照は、すべての移植可能なコードの中で避けるべきです。一部のプラットフォーム上では、-O2 よりも高い最適化レベルを使用してコンパイルすると、予測できない結果になることがあります。
f95 コンパイラでは、規格に準拠したプログラムのコンパイルを前提としています。Fortran の規格に厳密に準拠していないプログラムをコンパイルすると、コンパイラによる解析や最適化に支障を来す状況が生じることがあります。その状況によっては、誤った結果が得られる可能性があります。
たとえば、配列の境界を越えて添字を付ける、ポインタを使用する、または、大域的変数を直接使用しているときに副プログラムの引数としても渡すといったことが行われると、コンパイラの機能が制限されて、すべての状況で正しい最適なコードを生成できなくなる可能性があります。
プログラム内に別名参照が存在することが明らかな場合は、-xalias オプションを使用して、コンパイラが考慮すべきレベルを指定してください。適切な -xalias を指定しないで、-O2 よりも高い最適化レベルでコンパイルすると、場合によってはプログラムが正しく実行されなくなることがあります。
このオプションのフラグには、別名で参照する状況の種類を示すキーワードをコンマで区切って並べたリストを指定します。各キーワードには、別名参照が存在しないことを示す no% という接頭辞を付けることができます。
表 7–2 -xalias のキーワードとその意味
次に、別名参照が行われる一般的な状況の例を示します。f95 コンパイラで高い最適化レベル (-O3 以上) でコンパイルする場合は、次に示す別名参照の状況がプログラムに含まれないようにし、-xalias=no%keyword を使用してコンパイルした方が、より良いコードを生成できます。
場合によっては、生成したコードによって正しい結果が得られるようにするために、-xalias=keyword を使用してコンパイルする必要があることがあります。
次の例の場合は、-xalias=dummy を使用してコンパイルする必要があります。
parameter (n=100) integer a(n) common /qq/z(n) call sub(a,a,z,n) ... subroutine sub(a,b,c,n) integer a(n), b(n) common /qq/z(n) a(2:n) = b(1:n-1) c(2:n) = z(1:n-1) コンパイラは、仮変数や共通の変数がオーバーラップする可能性があることを前提と する必要があります。 |
この例が有効なのは、-xalias=craypointer (デフォルト) を使用してコンパイルした場合だけです。
parameter (n=20) integer a(n) integer v1(*), v2(*) pointer (p1,v1) pointer (p2,v2) p1 = loc(a) p2 = loc(a) a = (/ (i,i=1,n) /) ... v1(2:n) = v2(1:n-1) コンパイラは、これらの場所がオーバーラップしている可能性があることを前提とす る必要があります。 |
次に、オーバーラップしない Cray ポインタの例を示します。この場合は、-xalias=no%craypointer を使用してコンパイルします。この方が、より良いパフォーマンスを期待できます。
parameter (n=10) integer a(n+n) integer v1(n), v2(n) pointer (p1,v1) pointer (p2,v2) p1 = loc(a(1)) p2 = loc(a(n+1)) ... v1(:) = v2(:) Cray ポインタは、オーバーラップしたメモリー領域を指していません。 |
次の例を、-xalias=ftnpointer を使用してコンパイルします。
parameter (n=20) integer, pointer :: a(:) integer, target :: t(n) interface subroutine sub(a,b,n) integer, pointer :: a(:) integer, pointer :: b(:) end subroutine end interface a => t a = (/ (i, i=1,n) /) call sub(a,a,n) .... end subroutine sub(a,b,n) integer, pointer :: a(:) real, pointer :: b(:) integer i, mold forall (i=2:n) a(i) = transfer(b(i-1), mold) コンパイラは、a と b がオーバーラップする可能性があることを前提とする必要があり ます。 |
この例では、コンパイラは、a と b が別のデータ型のデータを指していてもオーバーラップする可能性があることを前提とする必要があります。これは、Fortran 規格に反しています。コンパイラは、このような状況を検出すると警告を出力します。
次の例を、-xalias=overindex を使用してコンパイルします。
integer a,z common // a(100),z z = 1 call sub(a) print*, z subroutine sub(x) integer x(10) x(101) = 2 コンパイラは、副プログラムの呼び出しが z への書き込みを引き起こす可能性がある と想定する可能性があります。 -xalias=overindex を使用してコンパイルすると、プログラムは 1 ではなく 2 を 出力します。 |
配列の境界を越えた添字付けは古い FORTRAN 77 のプログラムの多くに含まれていますが、これらは避けるべきものです。多くの場合は、結果を予測することができません。正しい結果が得られることを確かめるには、プログラムをコンパイルし、-C (境界を越えている添字の検査) オプションを使用してテストします。これによって、配列の添字に問題があるかどうかを調べることができます。
一般に、overindex は古い FORTRAN 77 のプログラムにだけ使用することをお勧めします。-xalias=overindex は、配列、構文式、部分配列、WHERE 文、FORALL 文には適用されません。
正しいコードが生成されるように、Fortran 95 のプログラムを常に Fortran 規格の添字の規則に準拠させるようにしてください。たとえば、次の例では、配列構文式の中で不明確な添字付けが行われているため、常に、配列の境界を越えた添字付けによる誤った結果が得られます。
配列の境界を越えて添字が付けられるこの配列構文の例では、正しい結果が得られません。 parameter (n=10) integer a(n),b(n) common /qq/a,b integer c(n) integer m, k a = (/ (i,i=1,n) /) b = a c(1) = 1 c(2:n) = (/ (i,i=1,n-1) /) m = n k = n + n C C a への参照は、実際には b を参照しています。 C これは本来は b(2:n) = b(1:n-1) であるべきです。 C a(m+2:k) = b(1:n-1) C またはこれを逆に行います。 a(k:m+2:-1) = b(n-1:1:-1) ユーザーは直感的に、配列 b が今度は配列 c のようになることを期待しますが、実際 の結果は予測できません。 |
overindex フラグは配列構文式には適用されないため、xalias=overindex フラグを使用してもこの状況は修正されません。この例をコンパイルすることはできますが、生成されたコードからは正しい結果を得られません。この例を書き換えて、配列構文を等価な DO ループに置き換えると、-xalias=overindex を使用してコンパイルしたときにこのフラグが働くようになります。しかし、このようなプログラミングはそもそも避けるべきです。
コンパイラは、局所変数がどのように使用されるかを予測して、副プログラムの呼び出しによって変更されない変数について仮説を立てます。次の例では、副プログラムで使用されているポインタが原因で、コンパイラによる最適化処理が正しく行われず、結果が予測できないものになっています。正しい結果が得られるようにするには、-xalias=actual フラグを使用してコンパイルする必要があります。
program foo integer i call take_loc(i) i = 1 print * , i call use_loc() print * , i end subroutine take_loc(i) integer i common /loc_comm/ loc_i loc_i = loc(i) end subroutine take_loc subroutine use_loc() integer vi1 pointer (pi,vi) common /loc_comm/ loc_i pi = loc_i vi1 = 3 end subroutine use_loc |
take_loc が i のアドレスを取得して保存し、use_loc がそれを使用しています。これは、Fortran 規格に反しています。
-xalias=actual を使用してコンパイルすると、副プログラムへのすべての引数がそのコンパイル単位内の大域的な変数として見なされるべきであるとコンパイラに伝えられます。 このため、コンパイラは、実引数のように見える変数について仮説を立てるときに、より慎重になります。
Fortran 規格に反するこのようなプログラミングは避けるべきです。
リストを付けないで -xalias を指定した場合は、そのプログラムが Fortran の別名参照の規則に反していないものと仮定されます。これは、別名参照の全キーワードに no% が付いていると仮定するのと同じことです。
-xalias を指定しないでコンパイルする場合のコンパイラのデフォルトは次のとおりです。
-xalias=no%dummy,craypointer,no%actual,no%overindex,no%ftnpointer
Cray ポインタを使用していても、Fortran の別名参照の規則に準拠している場合、つまり、不明確な状況でもポインタの参照によって別名参照が行われない場合は、-xalias を使用してコンパイルすることによって、より良い性能のコードが生成される可能性があります。
古いコードには、古いベクトル化コンパイラに特定のアーキテクチャーに最適なコードを生成させるための、通常の計算の DO ループを再構成しているソースコードが含まれていることがあります。ほとんどの場合、この再構成は必要がないもので、しかもプログラムの移植性を下げます。よく使用される再構成は、Strip-mining (ストリップマイニング) とループの展開の 2 つです。
いくつかのアーキテクチャー上では、固定長のベクトルレジスタのために、プログラマは手作業でループ内の配列計算について、セグメントの中にストリップマイニングをしなければいけませんでした。
REAL TX(0:63) ... DO IOUTER = 1,NX,64 DO IINNER = 0,63 TX(IINNER) = AX(IOUTER+IINNER) * BX(IOUTER+IINNER)/2. QX(IOUTER+IINNER) = TX(IINNER)**2 END DO END DO |
ストリップマイニングは最近のコンパイラには適切でありません。このループは、次のようにより明瞭に書くことができます。
DO IX = 1,N TX = AX(I)*BX(I)/2. QX(I) = TX**2 END DO |
以前、手作業によるループの展開はソースコード最適化のための典型的なテクニックでした。しかし、現在はコンパイラがこの再構成を自動的に行います。次にループの例を示します。
DO K = 1, N-5, 6 DO J = 1, N DO I = 1,N A(I,J) = A(I,J) + B(I,K ) * C(K ,J) * + B(I,K+1) * C(K+1,J) * + B(I,K+2) * C(K+2,J) * + B(I,K+3) * C(K+3,J) * + B(I,K+4) * C(K+4,J) * + B(I,K+5) * C(K+5,J) END DO END DO END DO DO KK = K,N DO J =1,N DO I =1,N A(I,J) = A(I,J) + B(I,KK) * C(KK,J) END DO END DO END DO |
前述のループは、本来意図していたとおり、次のように書き換えるべきです。
DO K = 1,N DO J = 1,N DO I = 1,N A(I,J) = A(I,J) + B(I,K) * C(K,J) END DO END DO END DO |