インテル® Xeon Phi™ コプロセッサーへの Fortran プロジェクトの移行
この記事は、インテル® デベロッパー・ゾーンに掲載されている「Migrating Fortran projects to the Intel® Xeon Phi™ coprocessor」の日本語参考訳です。
この記事では、インテル® Xeon Phi™ コプロセッサーへの Fortran コードの移行について取り上げます。コプロセッサー向けの多くのドキュメントは C/C++ を対象にしていますが、ここでは、現在でも科学プログラミング向けの主流言語であり、大量のコードが存在する Fortran に焦点を当てています。
ネイティブモデルとオフロード・プログラミング・モデル
コードをコプロセッサー上で実行するには、ネイティブモデルとオフロード・プログラミング・モデルの選択肢があります。従来の Fortran コードは、一般に修正を加えることなくコプロセッサー上でネイティブに実行できます。ただし、最良のパフォーマンスを得るには、コードをベクトル化およびマルチスレッド化する必要があります。
マルチスレッド・コードに大きなシングルスレッド・コード領域が多く含まれる場合、オフロード・プログラミング・モデルによって最良のパフォーマンスを得られるでしょう。ただし、大きな配列がホストとコプロセッサー間で繰り返し転送されるオフロードモデルは推奨されません。Fortran には C/C++ で提供される仮想共有型オフロード機能は用意されていません。仮想共有型オフロードモデルでは、値が変更されたデータ要素のみ、オフロードセクションの最初と最後で転送されます。Fortran では、データの転送を制御する NOCOPY、IN、OUT キーワードが指定される場合を除いて、オフロード宣言子にリストされているすべてのデータが転送されます。そのため、デフォルトの動作に依存するのではなく、可能な限り NOCOPY、IN 、OUT を使用することが重要です。
マルチスレッド化
マルチスレッド化には、OpenMP*、Pthreads*、および MPI を利用できます (C/C++ でも同様)。OpenMP* は従来の Fortran コードで最も一般的なスレッドモデルであり、続いて、MPI、Pthreads* の順になります。
インテル® Fortran コンパイラーでは、バージョン 14.0 から Co-Array と DO CONCURRENT 構造を使用できます。バージョン 14.0 以前でも、インテル® Xeon® プロセッサー上では Co-Array を使用できましたが、インテル® Xeon Phi™ コプロセッサー上では使用できませんでした。ただし、現時点では、ホストのオペレーティング・システムが Linux* であること、そして分散メモリーモデルのみ利用可能、という 2 つの制限があります。
Co-Array プログラムは、1) コプロセッサー上でネイティブに実行できます。 2) 1 つ以上の Co-Array イメージをホストで実行し、ほかのイメージを 1 つ以上のコプロセッサーで分散して実行できます。 3) 個々のイメージをホストで実行し、1 つ以上のコプロセッサーにワークをオフロードして実行できます。使用するモデルの決定方法は、MPI コードを実行する場合と似ています。単一コプロセッサーに複数の Co-Array イメージを配置するとメモリー要件は増加しますが、通信コストは低下します。MPI と同様に、OpenMP* を Co-Array プログラムの個々のイメージ内で使用し、コプロセッサー上で実行するスレッドの数を増やしてすべてのコアをビジーに保つことができます。
モジュール
プロシージャーがモジュールに含まれる場合、関数、サブルーチン、または変数をオフロードするには、
!DIR$ ATTRIBUTES OFFLOAD:mic :: MY_PROCEDURE_NAME
を FUNCTION または SUBROUTINE 文の直前に追加します。
モジュールを使用するプロシージャーに別の属性宣言子を追加する必要はありません。モジュール内の宣言子は、モジュールを使用するプロシージャーから参照できます。必須ではありませんが、単一モジュール内にオフロードされる関数を配置することはプログラマーのメリットになることがあります。同様に、モジュール内の変数のオフロード属性をモジュールのデータセクションの先頭に配置すると良いでしょう。モジュールを使用するプロシージャーに変数のオフロード属性文を追加する必要はありません。
モジュール全体にオフロード属性を適用することもできます。この場合、モジュールを使用するプロシージャーの USE 文の前で属性宣言を行います。
共通ブロック
オフロードコードで共通ブロックを使用する場合、オフロードセクションでは必要な変数のみをオフロード属性で明示する必要があります。共通ブロックが複数のオフロード・プロシージャーで使用される場合、属性宣言子と共通ブロックを各プロシージャーで使用されるインクルード・ファイルに宣言します。ただし、異なる変数が各ルーチンで使われる場合、個々の属性宣言子を各ルーチンに直接配置し、そのプロシージャーの要求に合わせて変更できます。共通ブロックの使用に関する詳細な例は、leoF02_global_common.inc および leoF02_global.F90 を参照してください。
コプロセッサー上の共通ブロックは Fortran 標準に従います。そのため、共通ブロックのすべての変数にオフロード属性が明示されていない場合でも、共通ブロック全体の領域がコプロセッサーに割り当てられます。ただし、オフロード属性が明示されていない変数については、ホストの変数アドレスからコプロセッサーの変数アドレスへマッピングされません。コプロセッサー上でその変数名を使おうとすると、コンパイラーは変数がオフロード用に明示されていない旨の警告を出力します。コンパイラーはそのメモリーにアクセスする等価な文を使用できるようにしますが、この文を使用することは非常に危険です。この領域に格納された値は、オフロードセクションの最後でホストに転送されません。この機会にコード全体を確認し同等の文を削除することをお勧めします。
ポインター
C/C++ では、ポインターをオフロードセクションに渡したり、オフロードセクションから受け取ることはできません。しかし、Fortran のポインターは、使用されるたびに自動的に逆参照されるため、オフロード文のデータ転送セクションで指定することができます。ポインターは逆参照され、該当する領域がコプロセッサー上で割り当てられます。
では、ポインターがポインターを含む派生型の構造または要素を指す場合はどうなるでしょうか。この場合の動作は、コンパイラー 14.0 で変更されています。
非同期データ転送
非同期データ転送を行うときは、データを参照するオフロード宣言子に SIGNAL と WAIT オプションを追加する必要があります。SIGNAL と WAIT 変数の値は、固有の整数値です。特定のタグに値を渡さなくても済むように、渡されたデータの最初の要素では値をホストのアドレスに設定することを推奨します。Fortran でこれを設定する最も簡単な方法は、LOC 組込み関数を利用することです。この組込み関数は Fortran 標準の一部ではありませんが、インテル® Fortran コンパイラーに含まれており、一般に利用可能です。
次のコードは、my_data および my_result の位置を使用して signal へ非同期データ転送を行っています。my_data および my_result にコプロセッサーの領域を割り当て、my_data の内容をコプロセッサーに転送します。転送が行われている間、ホストで作業を続行できます。タグ上で待機している次のオフロード宣言子にコードが達すると、最初の転送が完了しているかを確認します。転送が完了したら、いくつかの作業をコプロセッサーにオフロードします。オフロード宣言子に signal オプションを追加した場合、ホストはコプロセッサーから転送されるオフロードセクションの出力を待機しながら、さらに作業を実行できます。ホストはその作業を完了すると、出力がホストに転送されるのを待機して、コプロセッサーの領域を解放します。
SUBROUTINE async_example(my_data, my_result, cnt)
INTEGER cnt
INTEGER my_data[cnt], my_result[cnt]
INTEGER(INT_PTR_KIND()) my_data_loc, my_result_loc
my_data_loc = LOC(my_data)
my_result_loc = LOC(my_result)
!DIR$ OFFLOAD_TRANSFER TARGET(mic:0) &
IN(my_data : LENGTH(cnt) ALLOC_IF(.true.) FREE_IF(.false.)) &
NOCOPY(my_result : LENGTH(cnt) ALLOC_IF(.true.) FREE_IF(.false.))&
SIGNAL(my_data_loc)
...ホスト上の作業をここで行う...
!DIR$ OFFLOAD BEGIN TARGET(mic:0) WAIT(my_data_loc) SIGNAL(my_result_loc) &
NOCOPY(my_data : ALLOC_IF(.false.) FREE_IF(.false.)) &
OUT(my_result : ALLOC_IF(.false.) FREE_IF(.false.))
...オフロードした作業をここで行う...
!DIR$ END OFFLOAD
...ホスト上の作業をここで行う...
!DIR$ OFFLOAD_TRANSFER TARGET(mic:0) WAIT(my_result_loc) &
NOCOPY(my_data, my_result : ALLOC_IF(.false.) FREE_IF(.true.))
END SUBROUTINE async_example
ベクトル化可能な関数およびサブルーチン
インテル® Fortran コンパイラーは、ベクトル化可能な関数およびサブルーチンを記述する手法を提供します。SIMD 対応関数やサブルーチンとして知られるこれらの関数/サブルーチンは、ベクトル属性を使用して記述します。
SUBROUTINE MY_SIMD_ROUTINE(dummy1, dummy2, etc)
!DIR$ ATTRIBUTES VECTOR :: MY_SIMD_ROUTINE
…
END SUBROUTINE MY_SIMD_ROUTINE
この種のプロシージャーは、インテル® Xeon Phi™ コプロセッサーとインテル® Xeon® プロセッサーの両方で使用できます。
オフロードモードで実行するコードで、ベクトル化可能な関数またはサブルーチンは、ほかのプロシージャーと同様にオフロード属性が必要です。
プロシージャー定義とプロシージャー呼び出しがどちらも同じファイルに含まれる場合、その両方にベクトル属性を追加する必要はありません (追加しても誤りではありません)。しかし、別々のファイルに存在する場合は、どちらにもベクトル属性宣言子を追加する必要があります。オフロード属性宣言子は、常にプロシージャー定義の中とプロシージャー呼び出しの前の両方で指定します。ベクトル属性を使用する場合は次の手順で行うことを推奨します。
1) 属性宣言子にオフロード属性を追加します。
!DIR$ ATTRIBUTES VECTOR, OFFLOAD:mic :: MY_SIMD_ROUTINE
必要に応じて、ベクトル属性にアーキテクチャーの種類を追加します。その場合、プロセッサーとコプロセッサーの両方のアーキテクチャーを指定してください。
!DIR$ ATTRIBUTES VECTOR:(PROCESSOR(core_2nd_gen_avx),PROCESSOR(mic)), OFFLOAD:mic :: MY_SIMD_ROUTINE
2) SUBROUTINE または FUNCTION 文の直後、そしてこの SUBROUTINE または FUNCTION を呼び出す各プロシージャーの内部に宣言子を配置します。
配列表記 (アレイ・ノーテーション)
コプロセッサーの配列表記に関する多くのドキュメントではインテル® Cilk™ Plus の配列表記について説明していますが、Fortran プログラマーの皆さま、ご安心ください。Fortran の配列表記はインテル® Cilk™ Plus の配列表記ではなく Fortran 標準の配列表記を使用します。例えば、オフロード文
!dir$ offload begin in(foo(1:6:2))
の foo(1:6:2) は、要素 1 から要素 6 までのストライド 2 の配列になります。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。