ここで説明する配列アクセス手法の多くは、インテル® Fortran のループ変換最適化により自動的に適用されます。配列の使用法によって、ランタイム・パフォーマンスを向上することができます。
配列へのアクセスは、配列全体、または配列の大部分への連続的なアクセスが行われたときに、最も高速になります。分散した配列要素を何回も操作するよりも、配列全体または配列の大部分にアクセスする 1、2 回の配列操作を実行するようにします。配列アクセスは、明示的なループを使用するよりも、次の行のように配列変数 a のすべての要素をインクリメントするような基本的な配列操作を行うようにします。
a = a + 1
配列の読み取りや書き出しを行うときには、配列名を使用し、個々の要素番号を指定する DO ループや暗黙的な DO ループは使用しないようにします。Fortran 95/90 の配列構文では、式中で配列名を使うことで、配列全体を参照することができます。次に例を示します。
real :: a(100,100)
a = 0.0
a = a + 1 !Increment all elements
!of a by 1
.
.
.
write (8) a !Fast whole array use
同様に派生型配列構造体要素は、次のように使用できます。
type x
integer a(5)
end type x
.
.
.
type (x) z
write (8)z%a !Fast array structure
! component use
多次元配列が正しい配列構文で参照されていて、Fortran の自然昇順の記憶領域順である列優先順でトラバースされていることを確認します。列優先順では、一番左の添字が最も急速に 1 ずつ変化していきます。配列全体のアクセスには列優先順が使用されます。
C によって行われるような一番右の添字が最も急速に変化する行優先順の使用は避けてください。
次の例は、J ループを最も内側のループとして、2 次元配列を参照するネストされた DO ループを示します。
integer x(3,5), y(3,5), i, j
y = 0
do i=1,3 !I outer loop varies slowest
do j=1,5 !J inner loop varies fastest
x (i,j) = y(i,j) + 1 !Inefficient row-major storage order
end do !(rightmost subscript varies fastest)
end do
.
.
.
end program
最も急速に変化する j が、式 x (i,j) の第 2 配列添字であるため、配列アクセスは行優先順で行われます。
配列アクセスを自然な列優先順で行うためには、配列のアルゴリズムと変更されるデータを確認します。配列 x と y を使っている場合、最も内側のループ変数が一番左の配列次元に対応するように DO ループのネスト順序を変更することで、配列アクセスを自然な列優先順で行うことができます。
integer x(3,5), y(3,5), i, j
y = 0
do j=1,5 !J outer loop varies slowest
do i=1,3 !I inner loop varies fastest
x (i,j) = y(i,j) + 1 !Efficient column-major storage order
end do !(leftmost subscript varies fastest)
end do
.
.
.
end program
インテル Fortran の配列全体へのアクセス ( x = y + 1 ) では、効率的な列優先順が使われます。ただし、そのアプリケーションで J を最も急速に変化させる必要がある場合や、結果を変えることなくループの順序を変更することができない場合、アプリケーション・プログラムを変更して、配列次元の順序を変えることを検討してください。プログラムを変更するときには、次の順序を変える必要があります。
配列 x(5,3) と y(5,3) の宣言中の次元
do ループ内の x(j,i) と y(j,i) の代入
配列 x と y に対するその他すべての参照
ここでは、J が最も内側のループの場合、元の DO ループのネストが使われます。
integer x(3,5), y(3,5), i, j
y = 0
do i=1,3 !I outer loop varies slowest
do j=1,5 !J inner loop varies fastest
x (j,i) = y(j,i) + 1 !Efficient column-major storage order
end do !(leftmost subscript varies fastest)
end do
.
.
.
end program
多次元配列へのアクセスを行優先順 (C など) またはランダムな順で行うように書かれたコードでは、一般に CPU のメモリ・キャッシュが非効率的に使用されます。レコード I/O 操作時の自然な記憶順についての詳細は、「入出力性能の向上」を参照してください。
独自のプロシージャを作成するよりも、利用可能な Fortran 95/90 配列組込みプロシージャを使用します。
可能な限り同じタスクを実行するために、独自のルーチンを作成するよりも、Fortran 95/90 配列組込みプロシージャを使用するようにします。Fortran 95/90 配列組込みプロシージャは、さまざまなインテル Fortran ランタイム・コンポーネントで効率的に使用できるように設計されています。
また、標準準拠の配列組込み関数を使用することで、プログラムの移植性を高めることができます。
配列要素へのアクセスが非連続に行われる多次元配列では、一番左の配列次元が 2 の累乗 (256、512 など) にならないようにします。
キャッシュ・サイズは 2 の累乗であるため、配列次元が同様に 2 の累乗になっていると、配列アクセスが非連続な場合にキャッシュの使用効率が低下することがあります。キャッシュ・サイズが一番左の次元の倍数である場合、プログラムによるキャッシュの使用は非効率的になります。ただし、これは連続的でシーケンシャルなアクセスや配列全体へのアクセスには適用されません。
回避策の 1 つとして、次元を増やして、いくつかの使用されない要素を追加し、一番左の次元を実際に必要な大きさより大きくします。例えば、A の左端の次元を 512 から 520 へ増やすことで、キャッシュをより効率的に使用できます。
real a(512, 100)
do i= 2,511
do j = 2,99
a(i,j)=(a(i+1,j-1) + a(i-1, j+1)) * 0.5
end do
end do
このコードでは、配列 a の一番左の次元は 512 で、2 の累乗です。最も内側のループは一番右の次元にアクセスするので (行優先)、非効率なアクセスになります。a の一番左の次元を 520 (real a (520,100)) に増やすことで、実際には使われない要素が生じる代わりに、ループのパフォーマンスが向上します。
ループ・インデックス変数 I および J は、計算で使われるため、do ループのネスト順を変更すると結果が変わってしまいます。
配列およびそれらのデータ宣言文については、『Intel ® Fortran Language Reference』(英語) マニュアルを参照してください。
Fortran の配列引数には、2 つの一般的な形式があります。
FORTRAN 77 で使われる明示形状配列
これらの配列は次元数と範囲が固定されており、コンパイル時に認識されます。形状無指定ではないその他の仮引数 (受け取り側) 配列 (大きさ引継ぎ配列など) は、明示形状配列引数の形式に含めることができます。
Fortran 95/90 で導入された形状無指定配列
形状無指定配列の形式には、配列ポインタと割付け配列があります。形状引継ぎ配列引数は通常、形状無指定配列引数の受け渡しについての規則に従います。
配列を引数として渡す場合、配列の開始 (ベース) アドレスか、配列記述子のアドレスを渡します。
明示形状配列 (または大きさ引継ぎ配列) を使用して配列を受け取る場合は、配列の開始アドレスが渡されます。
形状無指定配列または形状引継ぎ配列を使用して配列を受け取る場合は、配列記述子のアドレスが渡されます (配列記述子はコンパイラが作成します)。
形状引継ぎ配列または配列ポインタを明示形状配列に渡すと、ランタイム・パフォーマンスが低下することがあります。これは、コンパイラが配列全体に対して一時的な配列を作成しなければならないためです。一時的な配列が作成されるのは、渡される配列が連続していない可能性があり、受け取り側の (形状明示) 配列が連続した配列を必要とするからです。一時的な配列が作成される場合、渡される配列のサイズによって、ランタイム・パフォーマンスに与える影響の大きさが決まります。
下記の表に、配列型の組み合わせによって行われる処理についての要約を示します。ランタイム・パフォーマンスの非効率性は、配列のサイズに依存します。
実引数配列の形式 |
実引数配列の形式 |
|
明示形状配列 |
形状無指定と形状引継ぎ配列 |
|
明示形状配列 |
この組み合わせを使用した結果: きわめて効率的。一時的な配列は使用しません。配列記述子は渡しません。インターフェイス・ブロックはオプションです。 |
この組み合わせを使用した結果: 効率的。形状引継ぎ配列でのみ可能です (形状無指定配列では不可)。一時的な配列は使用しません。配列記述子を渡します。インターフェイス・ブロックは必須です。 |
形状無指定と形状引継ぎ配列 |
この組み合わせを使用した結果: 割付け配列の受け渡しでは非常に効率的。一時的な配列は使用しません。配列記述子は渡しません。インターフェイス・ブロックはオプションです。 割付け配列を渡さない場合は、非効率的です。可能な限り、割付け配列を使用するようにしてくださ い。 一時的な配列を使用します。配列記述子は渡しません。インターフェイス・ブロックはオプションです。 |
この組み合わせを使用した結果: 効率的。仮引数として形状引継ぎまたは配列ポインタが必要です。一時的な配列は使用しません。配列記述子を渡します。インターフェイス・ブロックは必須です。 |