ループツールとループレポートは、特定のループに対して適用された最適化についてのヒント、そして、なぜ並列化が行われなかったかについてのヒントを提示します。最適化の間にコンパイラが生成するヒントは、わかりにくい場合もあります。こうしたヒントは全体の文脈の中で理解されるべきものであり、特定のループに対して生成されるコードについての絶対的な事実ではありません。ただし、コンパイラがループの並列化を含めたより一層の最適化を実行できるように、コードを変換する方法についての重要な指針となることも少なくありません。
並列化の上で参考になる説明とヒントについては、『 Fortran ユーザーズガイド』を参照してください。
表 3-3 には、ループに適用される最適化についてのヒントを示します。
表 3-3 ループ最適化のヒント
ヒント番号 |
ヒントの定義 |
---|---|
0 |
ヒントはありません。 |
1 |
ループ中に手続き呼び出しがあります。 |
2 |
コンパイラはこのループに対して 2 つのバージョンを生成しました。 |
3 |
ループ中に、変数 "%s" に対するデータの依存関係があります。 |
4 |
最適化の間に、ループが大幅に変形されています。 |
5 |
ループは、並列化動作によって効果が得られるかどうか不明です。 |
6 |
ループにユーザーが挿入した DOALL プラグマが付いています。 |
7 |
ループ中に複数の出口があります。 |
8 |
ループ中にマルチスレッドで安全でない I/O またはほかの関数呼び出しがあります。 |
9 |
ループ中に制御が逆方向に進む箇所があります。 |
10 |
ループは分散して実行されている可能性があります。 |
11 |
複数のループが融合されている可能性があります。 |
12 |
複数のループが交換されている可能性があります。 |
このループに適用されるヒントはありません。これは、その他のヒントがまったく適用されないという意味ではなく、単にコンパイラから導かれるヒントがないということです。
ループ中にマルチスレッドで動作すると安全でない手続き呼び出しが含まれているので、並列化できません。このようなループを並列化すると、ループの複数のコピーによって関数呼び出しが同時にインスタンス化され、この関数に固有の変数や戻り値に悪影響を与えたり、関数の目的を無効にする可能性があります。このループ中の手続き呼び出しがマルチスレッドで使用しても安全なことがわかっている場合は、ループ本体の前に DOALLプラグマを追加することにより、このループを無条件に並列化するよう指示できます。たとえば、foo がマルチスレッドで使用しても安全な関数呼び出しである場合、c$par DOALL を挿入することによって、強制的な並列化を指定できます。
c$par DOALL do 19 i = 1, n*n do 29 k = i, n*n a(i) = a(i) + x*b(k)*c(i-k) call foo() 29 continue 19 continue
コンパイラは -parallel または -explicitpar を指定してコンパイルした場合に限り、DOALL プラグマを解釈します。-autopar を指定してコンパイルすると、DOALLプラグマは無視されます。
コンパイル時には、ループの中に並列化する意味のある要素が含まれているかどうか、コンパイラは判断できません。コンパイラは、逐次形式と並列形式の 2 つのバージョンのループを生成します。実行時の検査によって、どちらのバージョンを使用するかを決定します。この実行時検査は、ループの繰り返し値を検査して、ループの処理量を判断します。
ループ中の変数が、前回の繰り返しに使用された変数の値による影響を受けます。たとえば、次のとおりです。
do 99 i=1,n do 99 j = 1,m a[i, j+1] = a[i,j] + a[i,j-1] 99 continue
これは、あくまで説明の便宜上、意図的に考えた例にすぎません。このような単純なループの場合は、単にオプティマイザによって、内側と外側のループを入れ替えて、内側のループを並列化できますが、この例は、「ループ伝播データ依存性」と呼ばれるデータの依存関係の概念をわかりやすく示したものでもあります。
コンパイラは、ループ伝播データ依存性の原因となっている変数の名前を特定できる場合があります。プログラムを変更して、この種の依存関係を取り除く (または最小限に抑える) ことにより、コンパイラはさらに積極的な最適化を実行できるようになります。
コンパイラがこのループを最適化した結果、生成されたコードとソースコードの対応が取れなくなる場合があります。このため、行番号の整合性がなくなっている可能性があります。ループを大幅に変更する可能性のある最適化の例として、ループの分散化、融合、交換 (「10. ループは分散して実行されている可能性があります。」、 「11. 複数のループが融合されている可能性があります。」、 および 「12. 複数のループが交換されている可能性があります。」 を参照) などがあります。
コンパイラは、ループが並列化動作によって効果が得られるかどうかを、コンパイル時には判断できません。このヒントが表示されたループは、場合によって、「並列」のラベルが付けられることがあります。これは、コンパイラが 2 つのバージョンのループを生成 (「2. コンパイラはこのループに対して 2 つのバージョンを生成しました。」 を参照) し、並列バージョンと逐次バージョンのいずれを使用するかは実行時に決定するという意味です。
コンパイラヒントは、ループの並列化の有無を示すフラグも含め、すべてコンパイル時に生成されるので、「並列」とラベル付けされたループが実際に並列的に実行されるかどうかについて確証はありません。
このループは、コンパイラに対する DOALL プラグマの指示によって並列化されています。このヒントは、明示的に並列化を指定したループを簡単に識別する場合に利用できます。
DOALL プラグマは、-parallel または -explicitpar を指定してコンパイルする場合に限り、コンパイラにより解釈されます。-autopar を指定してコンパイルすると、コンパイラは DOALL プラグマをすべて無視します。
ループの中に、通常のループの出口以外に、GOTO やループを終了するその他の分岐が使用されています。したがって、コンパイラではループの実行時の動作が予測できないため、ループの並列化は危険と判断されます。
これは、ヒント 1 に似ています。ただし、ヒント 8 がマルチスレッドで動作すると安全でない「入出力」に適用され、ヒント 1 はマルチスレッドで動作すると安全でない「関数の呼び出し」に適用される点が異なります。
ループの中に、GOTO や、逆方向に流れたりループ本体から抜けたりするような制御フローが使用されています。すなわち、コンパイラはループの内部にある文が、すでに実行済みのコードに戻るフローになっていると解釈します。複数の出口が存在するループの場合と同様に、このループを並列化することは安全ではありません。
逆方向の制御フローを取り除く (または最小限に抑える) ことにより、コンパイラはより積極的な最適化を実現できるようになります。
ループの内容が、何度か繰り返すうちに分散している可能性があります。すなわち、ループを並列化できるようにコンパイラによってループ本体が書き換えられている可能性があります。ただし、この書き換えはオプティマイザの内部表現で使用する言語によって行われるので、もとのソースコードと書き換えたコードを対応させるのはきわめて困難になります。このため、分散されたループについてのヒントでは、実際のソースコードにある行番号には一致しない行番号が示される可能性があります。
並列化の効果を高めるよう大きなループにするため連続した 2 つのループが 1 つにまとめられています。また、この場合も、ソースの行番号が誤っている可能性があります。
内部ループと外部ループのインデックスが入れ替えられています。これは、データの依存関係を内部ループから可能な限り遠い位置に置き、この入れ子ループを並列化するための処置として行うものです。深いレベルで入れ子にしたループの場合は、3 つ以上のループで交換が行われている可能性があります。