インテル® Fortran コンパイラー 18.0 デベロッパー・ガイドおよびリファレンス
自動ベクトル化は、IA-32 アーキテクチャーとインテル® 64 アーキテクチャーでサポートされています。ここでは、自動ベクトルの設定に役立つ情報を提供します。
ベクトル化によるスピードアップはどのようにもたらされるでしょうか? 次に示すコード例について考えてみます。a、b、c は整数配列です。
コード例 |
---|
|
ベクトル化が有効ではない場合 (つまり、O1 または [Q]vec- オプションを使用してコンパイルする場合)、コンパイラーはそれぞれの反復で各 SIMD レジスターに追加で 3 つの整数を保持できる空間があったとしても、それらを活用せずに (未使用のまま) コードを生成します。ベクトル化が有効な場合 (O2 以上のオプションを使用してコンパイルする場合)、コンパイラーは SIMD レジスターの未使用の空間を活用して、1 つの命令で 4 つの加算を実行します。デフォルトの最適化オプション (-O2) 以上でコンパイルすると、コンパイラーは常にベクトル化の可能性を探します。
このオプションを使用すると、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーの両方で、デフォルトの最適化レベルのベクトル化が有効になります。ベクトル化により呼び出されるライブラリー・ルーチンは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、ベクトル化は、/arch (Windows*)、-m (Linux* および macOS*)、[Q]x などの特定のオプションによる影響を受けます。
ベクトル化されたコードとベクトル化されていないコードを比較するために、/Qvec- オプション (Windows*) または -no-vec オプション (Linux* および macOS*) を使用してベクトル化を無効にしたり、O2 オプションで有効にしてみてください。
ループがベクトル化されたがどうかを確認するには、Qopt-report:1 Qopt-report-phase:vec (Windows*) または qopt-report=1 qopt-report-phase=vec (Linux* および macOS*) オプションを使用して、最適化化レポートを生成します。これらのオプションは、*.optrpt ファイルに最適化メッセージを含む異なるレポートを生成します。Visual Studio* では、ソースファイルにレポートメッセージのアノテーションが付けられます。生成された .optrpt ファイルをテキストエディターで開いて確認することもできます。次のように、ベクトル化されたループごとにメッセージが出力されます。
ベクトル化レポートの例 |
---|
|
ソースの行番号 (上記の例では 38) はループの始めか終わりを指します。
適用されたループ変換と最適化の種類を確認するには、[Q]opt-report-phase オプションだけを指定するか、[Q]opt-report オプションと一緒に指定します。
パフォーマンスの向上はどの程度のものでしょうか? パフォーマンスの向上を評価するには、以下の例 vec_samples を実行してみてください。
インテル® コンパイラーのコマンドウィンドウを開きます。
Windows*:[スタート] メニューのインテル製品項目にある [Compiler and Performance Libraries (コンパイラーおよびパフォーマンス・ライブラリー)] > [Command Prompt with Intel Compiler (インテル® コンパイラー用コマンドプロンプト)] からアイコンを選択します。
Linux* および macOS*: <installdir>/bin ディレクトリーにある compilervars.sh または compilervars.csh などの環境スクリプトを、アーキテクチャーに応じた属性を使用して source コマンドで実行します。
<install-dir>\Samples\<locale>\Fortran\ ディレクトリーに移動します。Windows* 上で、vec_samples.zip サンプル・プロジェクトを書き込み可能なディレクトリーに展開します。この小さなプログラム example1 は、次のループを使用してベクトルに行列を掛けます。
ベクトル行列乗算の例 |
---|
|
最初は自動ベクトル化を無効にして、プログラムをビルドし実行します。デフォルトの O2 最適化は、ベクトル化を有効にするため、別のオプションにより無効にする必要があります。プログラムの実行時間に注目してください。
自動ベクトル化なしのアプリケーションのビルドと実行の例 |
---|
|
|
次に自動ベクトル化を使用して、プログラムをビルドし実行します。プログラムの実行時間に注目してください。
自動ベクトル化ありのアプリケーションのビルドと実行の例 |
---|
|
|
2 つの実行時間を比較すると、ベクトル化されたバージョンのほうが速いことが分かるでしょう。ベクトル化されていないバージョンの実行時間は、O1 オプションでコンパイルした場合より多少早いだけです。
次に示す状況が必ずしもベクトル化を妨げるわけではありませんが、コンパイラーがベクトル化のメリットがないと判断する原因になります。
連続していないメモリーアクセス: 4 つの連続する int 型または float 型、あるいは 2 つの連続する double 型は、1 つの SSE 命令でメモリーから直接ロードされます。しかし、4 つの整数が隣接していなければ、複数の命令を使って別々にロードしなければならず、非効率的です。連続していないメモリーアクセスの最も一般的な例は、次に示すような非ユニットストライドのループや間接アドレス指定のループです。連続していないメモリーアクセスのオーバーヘッドと比較して計算量が多くない限り、コンパイラーはそのようなループをほとんどベクトル化しません。
連続していないメモリーアクセスの例 |
---|
|
通常、ベクトル化レポートには「ベクトル化は可能ですが非効率です」と出力されます。間接アドレス指定に対しては、「ベクトル依存関係が存在しています」というメッセージが出力されることもあります。
データ依存: 各 SIMD 命令は複数のデータ要素を一度に演算するため、ベクトル化ではループ内の演算順序が変わります。この順序の変更が演算結果に影響しない場合にのみ、ベクトル化が可能です。
最も単純なケースは、ループで書き込まれる (格納される) データ要素がそのループのほかの反復では使用されない場合です。この場合は、オリジナルのループのすべての反復が互いに独立しており、結果を変更することなくどの順序でも実行できます。このようなループは、ベクトル化を含むあらゆる並列手法で安全に実行することできます。これまでに示した例はすべてこのカテゴリーに分類されます。
変数が 1 つの反復で書き込まれ、後に続く反復で読み取られる場合は、"リードアフターライト" の依存性 (フロー依存性とも呼ばれる) があります。次に例を示します。
フロー依存性の例 |
---|
|
j の値はすべての A(J) に含まれます。この場合、安全にベクトル化できません。最初の 2 つの反復が SIMD 命令で同時に実行されると、最初の反復による演算前に、A(2) の値が 2 つ目の反復で使用されます。
変数が 1 つの反復で読み取られ、後に続く反復で書き込まれる場合は、"ライトアフターリード" の依存性 (アンチ依存とも呼ばれる) があります。次に例を示します。
ライトアフターリードの依存性の例 |
---|
|
このライトアフターリードの依存性の場合、書き込みの反復が、読み取りの反復前に実行される可能性があるため、一般的な並列実行では安全ではありません。しかし、この例では j の値が大きい反復が小さい反復よりも前に完了することはありません。そのため、この場合は安全にベクトル化できます (つまり、非ベクトルコードと同じ結果がもたらされます)。ただし、次の例では、ベクトル化により、A の要素が 2 つ目の反復で使用される前に最初の反復で上書きされ可能性があるため、安全ではありません。
安全ではないベクトル化の例 |
---|
|
リードアフターリードは、実際には依存ではなく、ベクトル化や並列実行を妨げるわけではありません。変数が書き込まれなければ、いくら読み取られても関係ありません。
同じ変数が複数の反復で書き込まれるライトアフターライト、または「出力」依存性は、一般にベクトル化を含む並列実行で安全ではありません。
上記の依存性タイプすべてを含む重要な例外があります:
依存性の例外 |
---|
|
MYSUM は各反復で読み取りと書き込みの両方が行われますが、コンパイラーはこのようなリダクション・スタイルを認識し、安全にベクトル化することができます。最初の例のループは、スカラーの代わりにループ不変配列要素を持つリダクションの別の例です。
このようなループ反復間の依存性は、ループ伝搬の依存としても知られています。
上記の例では依存性が証明されています。コンパイラーは依存性の可能性があると、安全にループをベクトル化できません。次の例について考えてみます。
依存性の可能性がある例 |
---|
|
上記の例では、コンパイラーは、反復 I で C(I) が別の反復の A(I) または B(I) と同じメモリー位置を参照するかどうかを判別しなければなりません。そのようなメモリー位置は "エイリアス" と呼ばれます。例えば、A(I) が C(I-1) と同じメモリー位置を指した場合、前述したようにリードアフターライトの依存性があります。この可能性をコンパイラーが排除できない場合、開発者がコンパイラーにヒントを提供しない限り、ループはベクトル化されません。 この問題は、配列を POINTER の代わりにコンパイラーがエイリアスできない ALLOCATABLE にすることでも回避できます。