この記事は、インテル® デベロッパー・ゾーンに掲載されている「Requirements for Vectorizable Loops」の日本語参考訳です。
ループのベクトル化
IA-32 またはインテル® 64 対応アプリケーション用インテル® C/C++ および Fortran コンパイラーにおけるループの「ベクトル化」とは、パックド SIMD 命令を利用して 1 つの命令で複数のデータ要素に対して同じ演算を実行可能にするループアンロールを指します。例えば、ベクトル化されていない “DAXPY” ループを考えてみます。
for (i=0;i<MAX;i++) z[i]=a*x[i]+y[i];
このループでは addsd や mulsd のようなスカラー SIMD 命令を利用します。一方、ベクトル化されたループは addpd や mulpd のようなパックド命令を利用します (命令の最後から 2 番目の文字 s は「スカラー」、p は「パックド」を表します。最後の文字 s は単精度、d は倍精度を表します。) 最新のインテル® コンパイラーでは、ベクトル化はデフォルトで有効となる多くの最適化の 1 つです。
ベクトル化は、オリジナルのループの連続する複数の反復の同時実行であると見なすことができます。インテル® ストリーミング SIMD 拡張命令対応のプロセッサーでは、通常 2 または 4 反復を同時に実行できますが、整数演算や将来の命令セットではこれよりも多くなります。この同時に実行可能な反復数は、ベクトル化可能なループの種類を制限します。効率良くベクトル化するためのさらなる条件は、SIMD 命令の特性により決まります。
ループをベクトル化するための条件:
- ループはシーケンシャル型コード (1 つの基本ブロック) でなければいけません。ジャンプや分岐を含むことはできませんが、if-then-else 構文を含むマスクされた割り当てや代入は許されます。
- ループは可算ループでなければいけません。つまり、コンパイル時に判明している必要はありませんが、ループに入る前に反復回数が判明していなければなりません。このため、データ依存性を持つ出口条件があってはいけません。
- 後方へのループ伝搬依存性があってはいけません。例えば、正しい結果を得るために、反復 2 のステートメント 1 よりも前に、反復 1 のステートメント 2 を実行する必要があってはいけません。これにより、アンロールされ、ベクトル化されたループの単一の反復で、オリジナルのループの連続する反復を同時に実行することができます。
ベクトル化可能: a[i-1] は常に参照される前に計算されます。
for (i=1; i<MAX; i++) { a[i] = b[i] + c[i] d[i] = e[i] – a[i-1] }
ベクトル化不可: (ベクトル化された場合) a[i-1] は計算される前に参照されます。
for (i=1; i<MAX; i++) { d[i] = e[i] – a[i-1] a[i] = b[i] + c[i] }
- 手動またはコンパイラーにより自動的にインライン展開される場合を除いて、ループに特殊演算子や関数呼び出し/サブルーチン呼び出しがあってはいけません。また、それらは SIMD (ベクトル化された) 関数である必要があります。sin()、log()、fmax() などの算術組込み関数は、コンパイラーのランタイム・ライブラリーに SIMD (ベクトル化された) バージョン (ベクトル・マス・ライブラリー) が含まれているため、利用できます。詳細なリストについては、コメント・セクションをご覧ください。
- 一般的に入れ子されたループの場合、内側のループでなければなりません。外側のループは OpenMP* または自動並列化 (-parallel) によって並列化できますが、コンパイラーが内側のループを完全にアンロールできる場合、または内側のループと外側のループを交換できる場合を除いて、自動ベクトル化することはできません。(このようなハイレベルのループ交換を追加で行う場合、–O3 オプションが必要になることがあります。このオプションはインテル製マイクロプロセッサーおよび互換マイクロプロセッサーで利用可能ですが、インテル製マイクロプロセッサーにおいてより多くの最適化が行われる場合があります)。外部ループのベクトル化を行うようにコンパイラーに指示するには、SIMD プラグマや宣言子を使用します。#pragma simd、!DIR SIMD また OpenMP 4.0 の SIMD 構文を使用してベクトル化できるループに関する詳細は、「#pragma SIMD を使用してループをベクトル化するための条件」をご覧ください。
アドバイス:
- 配列へのリダクションとベクトル代入はどちらも許可されています。
- 同じループの中にベクトル化できる複数のデータ型を混在させないでください (配列のインデックスに対する整数演算を除く)。型変換を伴うベクトル化は、効率が悪くなるか、型と操作によってはサポートされません。
- 連続するメモリー位置にアクセスするようにします (複数次元配列の場合、Fortran では最初の配列インデックス、C では最後の配列インデックスをループします)。コンパイラーは、間接または非ユニットストライドのメモリーアドレス指定を含むループをベクトル化できることもありますが、多くの場合データのギャザーとスキャッターに多大なコストがかかるためベクトル化しても利点が得られません。
- コンパイラーのベクトル化を支援するプラグマや宣言子を利用します。以下に例を示します。
- ivdep プラグマ/宣言子を用いて、安全なベクトル化の妨げになるループ伝搬依存性がないことをコンパイラーに知らせることができます。
- vector always プラグマ/宣言子を用いて、ループのベクトル化によりパフォーマンスの向上がもたらされるかどうかを判断するコンパイラーのヒューリスティックを変更して常にベクトル化を行うように指示します。このプラグマは、コンパイラーの依存関係解析を変更しません。
- ループがベクトル化されたかどうかと、ベクトル化できなかった場合その理由をベクトル化レポートで確認できます。レポートを出力するには、-qopt-report-phase=vec (Linux* および OS X*) または /Qopt-report-phase:vec (Windows*) オプションを指定します。レポートの詳細レベルは、-qopt-report=[1 – 5] (Linux* および OS X*) または /Qopt-report:[1 – 5] (Windows*) で変更できます。
- インテル® Cilk™ Plus の配列表記、SIMD 対応関数、そして SIMD プラグマや宣言子を使用して明示的なベクトル・プログラミングを行うことで、ループのベクトル化の可能性を高めます。
- 詳細は、『インテル® コンパイラー・ユーザー・リファレンス・ガイド』の「主な機能」>「自動ベクトル化」をご覧ください。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください