配列の効率的な使用

ここでは、配列への効率的なアクセス方法および配列引数の効率的な受け渡し方法について説明します。

効率的な配列アクセス

ここで説明する配列アクセス手法の多くは、インテル® 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 配列添字であるため、配列アクセスは行優先順 (row-major order) で行われます。配列アクセスを自然な列優先順で行うためには、配列のアルゴリズムと変更されるデータを確認します。配列 XY を使用している場合、最内ループ変数が一番左の配列次元に対応するように 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 を最も急速に変化させる必要がある場合や、結果を変えることなくループの順序を変更することができない場合、アプリケーションを変更して、配列次元の順序を変えることを検討してください。プログラムを変更するときには、次の順序を変える必要があります。

ここでは、J が最内ループの場合、元の DO ループの入れ子が使用されます。

INTEGER  X(5,3), Y(5,3), 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 配列組み込みプロシージャーを使用するようにします。Fortran 配列組み込みプロシージャーは、さまざまなインテル® 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 ループの入れ子順を変更すると結果が変わってしまいます。

配列およびそれらのデータ宣言文については、「Language Reference」(英語) マニュアルを参照してください。

配列引数の効率的な渡し方

Fortran の配列引数には、2 つの一般的な形式があります。

これらの配列は次元数と範囲が固定されており、コンパイル時に認識されます。形状無指定ではないその他の仮引数 (受け取り側) 配列 (大きさ引継ぎ配列など) は、明示形状配列引数の形式に含めることができます。

形状無指定配列の形式には、配列ポインターと割付け配列があります。形状引継ぎ配列引数は通常、形状無指定配列引数の受け渡しについての規則に従います。

配列を引数として渡す場合、配列の開始 (ベース) アドレスか、配列記述子のアドレスを渡します。

形状引継ぎ配列または配列ポインターを明示形状配列に渡すと、ランタイム・パフォーマンスが低下することがあります。これは、コンパイラーが配列全体に対して一時的な配列を作成しなければならないためです。一時的な配列が作成されるのは、渡される配列が連続していない可能性があり、受け取り側の (形状明示) 配列が連続した配列を必要とするからです。一時的な配列が作成される場合、渡される配列のサイズによって、ランタイム・パフォーマンスに与える影響の大きさが決まります。

下記の表に、配列型の組み合わせによって行われる処理についての要約を示します。ランタイム・パフォーマンスの非効率性は、配列のサイズに依存します。

仮引数配列の形式
(1 つ選択)

実引数配列の形式
(1 つ選択)

明示形状配列

形状無指定と形状引継ぎ配列

明示形状配列

この組み合わせを使用した結果: 極めて効率的。一時的な配列は使用しません。配列記述子は渡しません。

インターフェイス・ブロックはオプションです。

この組み合わせを使用した結果: 効果的。形状引継ぎ配列でのみ可能です (形状無指定配列では不可)。

一時的な配列は使用しません。配列記述子を渡します。

インターフェイス・ブロックは必須です。

形状無指定と
形状引継ぎ配列

この組み合わせを使用した結果: 割付け配列の受け渡しでは非常に効率的。一時的な配列は使用しません。配列記述子は渡しません。インターフェイス・ブロックはオプションです。

割付け配列を渡さない場合は、非効率的です。可能な限り、割付け配列を使用するようにしてください。

一時的な配列を使用します。配列記述子は渡しません。インターフェイス・ブロックはオプションです。

この組み合わせを使用した結果: 効果的。仮引数として形状引継ぎまたは配列ポインターが必要です。

一時的な配列は使用しません。配列記述子を渡します。

インターフェイス・ブロックは必須です。


このヘルプトピックについてのフィードバックを送信

© 1996-2010 Intel Corporation. 無断での引用、転載を禁じます。