nAG Fortran コンパイラは、Fortran 2008 標準で導入された並列プログラミングの機能、共配列(Coarray)をサポートしています。
共配列の並列実行を有効にするには、コンパイラオプション -coarray=cosmp を付加します。
また、コンパイラオプション -num_images で像(Image)の個数を指定することができます。
コンパイルコマンド
nagfor -coarray=cosmp -num_images=N ...
-num_images の像の個数 N は適宜指定してください。
コンパイラオプションの詳細は「nAG Fortran Compiler, Release 7.2 マニュアル - 2.4 コンパイラオプション」をご参照ください。
また、像の個数は、環境変数 nAGFORTRAN_NUM_IMAGES を用いて、実行時に指定することもできます。
環境変数の詳細は「nAG Fortran Compiler, Release 7.2 マニュアル - 2.19 実行時環境変数」をご参照ください。
プログラム例1:像
プログラムを並列実行すると、そのプログラムの複数のコピー(これを「像」と呼ぶ)が作られ、プログラマーが何らかの同期(制御)を行わない限り、各像は他の像とは独立に実行されます。
各像はそれぞれ一意の像番号を持っています。像番号は、1 から像の個数 N までの連続する正の整数(1, 2, ..., N)です。
像番号は組込み関数 this_image で取得することができます。
[ hello_image.f90 ]
program main implicit none print *, "Hello image", this_image() end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out hello_image.f90
実行結果の例:
Hello image 1 Hello image 3 Hello image 4 Hello image 2
プログラム例2:同期
像の同期は、像制御文を用いて行います。
最も基本的な像制御文は、バリア同期 sync all 文です。一つの像の sync all 文の前にある処理は、別の像の sync all 文の後にある処理より必ず先に実行されます。つまり、すべての像の処理が sync all 文に達するまで、sync all 文より後の処理はどの像においても実行されません。
以下は、sync all 文を用いたプログラム例です。
program main implicit none print *, "Hello image", this_image() sync all print *, "Goodbye image", this_image() end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out with_sync_all.f90
実行結果の例:
Hello image 2 Hello image 4 Hello image 1 Hello image 3 Goodbye image 3 Goodbye image 1 Goodbye image 2 Goodbye image 4
以下は、sync all 文を外したバージョンです。
program main implicit none print *, "Hello image", this_image() print *, "Goodbye image", this_image() end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out without_sync_all.f90
実行結果の例:
Hello image 1 Hello image 2 Goodbye image 2 Goodbye image 1 Hello image 4 Goodbye image 4 Hello image 3 Goodbye image 3
プログラム例3:共配列
像間のデータのやり取りは、共配列を用いて行います。任意の型のスカラまたは配列を共配列とすることができ、各像から他の像の変数にアクセスすることができます。
共配列の宣言と使用は鉤括弧を用いて行います。以下は、整数型スカラの共配列の宣言です。
integer :: x[*]
もしくは、codimension 属性を用いて宣言することもできます。
integer, codimension[*] :: x
以下では、像 3 の変数 x に数値 79 を代入しています。
x[3] = 79
以下では、像 5 の変数 x の値が出力されます。
print *, x[5]
以下は、共配列を用いたプログラム例です。ここで、num_images は、像の個数を取得する組込み関数です。
program main implicit none integer :: i, s[*] s = this_image() sync all if (this_image() == 1) then do i = 2, num_images() s = s + s[i] end do print *, "The sum of integers from 1 to", num_images(), "is", s end if end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out scalar_coarray.f90
実行結果の例:
The sum of integers from 1 to 4 is 10
プログラム例4:配列の共配列
スカラと同様に、配列の共配列も利用できます。以下は、実数型配列の共配列の宣言です。
real :: x(10)[*]
また、codimension 属性を用いて以下のように宣言することもできます。
real, dimension(10), codimension[*] :: x
以下では、像 3 の配列要素 x(8) に数値 79 を代入しています。
x(8)[3] = 79
以下では、像 5 の配列要素 x(2) の値が出力されます。
print *, x(2)[5]
以下は、配列の共配列を用いたプログラム例です。
program main implicit none integer, parameter :: n = 100 real :: a(n)[*] integer :: i call random_number(a) sync all if (this_image() == 1) then do i = 2, num_images() a = a + a(:)[i] end do print *, "Mean :", sum(a) / (n * num_images()) end if end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out array_coarray.f90
実行結果の例:
Mean : 0.5060082
プログラム例5:動的な配列の共配列
動的な配列の共配列を利用にするには、allocatable 属性を付加します。
real, allocatable :: x(:)[:]
また、codimension 属性を用いて以下のように宣言することもできます。
real, allocatable, dimension(:), codimension[:] :: x
割付けは以下のように行います。
allocate (x(10)[*])
なお、スコープ外に出ると割付けは自動的に解放されますので、再割付けなどの必要がなければ、deallocate を特に明示する必要はありません。
以下は、上記【プログラム例4】の動的バージョンです。
program main implicit none integer, parameter :: n = 100 real, allocatable :: a(:)[:] integer :: i allocate (a(n)[*]) call random_number(a) sync all if (this_image() == 1) then do i = 2, num_images() a = a + a(:)[i] end do print *, "Mean :", sum(a) / (n * num_images()) end if end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out allocatable_coarray.f90
実行結果の例:
Mean : 0.5060082
プログラム例6:高度な同期
像制御文には、sync all の他にも、sync images、critical / end critical、lock / unlock、sync memory 等があり、より細かな制御が可能です。
ここでは、sync images 文を取り上げます。sync images 文は、特定の像のセットの同期を取るために使われます。例えば、
① if (this_image() == 2) sync images(3)
上記 ① が実行されると、像 2 の処理は一時停止します。その後、対応する下記 ② が実行されると、像 2 の処理は再開します。
② if (this_image() == 3) sync images(2)
逆に、② が先に実行された場合、像 3 の処理は一時停止します。その後、対応する ① が実行されると、像 3 の処理は再開します。
以下は、sync images 文を用いたプログラム例です。このプログラムでは像の処理が、像 1、像 2、... と順番に行われます。
program main implicit none integer :: me, ne, p[*] me = this_image() ne = num_images() if (me == 1) then p = 1 print *, p else sync images (me - 1) p = p[me - 1] + 1 print *, p end if if (me < ne) sync images (me + 1) end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out sync_images_1.f90
実行結果の例:
1 2 3 4
また、以下のような定番のコーディングパターンもあります。
program main implicit none if (this_image() == 1) then print *, "Set up coarray data on image", this_image() sync images (*) else sync images (1) print *, "Use the data set up by image 1 on image", this_image() end if end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out sync_images_2.f90
実行結果の例:
Set up coarray data on image 1 Use the data set up by image 1 on image 4 Use the data set up by image 1 on image 2 Use the data set up by image 1 on image 3
プログラム例7:集団処理サブルーチン
集団処理サブルーチン(Collective subroutine)、co_broadcast、co_max、co_min、co_reduce、co_sum を使用すると、すべての像の通常の(共配列でない)変数の設定や集計などを行うことができます。
ここでは、co_broadcast を取り上げます。co_broadcast では、一つの特定の像の変数の値を、他のすべての像の変数に設定することができます。
以下は、co_broadcast を用いたプログラム例です。像 1 の配列 x の値が、他のすべての像の配列 x に設定されます。
ここで、配列 x は共配列ではないことに注意してください。また、同期を明示する必要もありません(コンパイラが行ってくれます)。
[ co_broadcast.f90 ]
program main implicit none integer :: x(3) if (this_image() == 1) then x = [1, 4, 9] end if call co_broadcast(x, 1) print *, "Image", this_image(), ":", x end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out co_broadcast.f90
実行結果の例:
Image 2 : 1 4 9 Image 1 : 1 4 9 Image 4 : 1 4 9 Image 3 : 1 4 9
プログラム例8:素朴な素数判定プログラム
program main implicit none integer :: i, j, n, me, ne logical :: is_prime[*] me = this_image() ne = num_images() if (me == 1) then do print *, "Please enter an integer n >= 2 :" read *, n if (n >= 2) exit end do end if call co_broadcast(n, 1) is_prime = .true. sync all i = me + 1 do if (i ** 2 > n) exit if (mod(n, i) == 0) then do j = 1, ne is_prime[j] = .false. end do end if if (is_prime .eqv. .false.) exit i = i + ne end do sync all if (me == 1) then if (is_prime .eqv. .true.) then print *, n, "is a prime number." else print *, n, "is not a prime number." end if end if end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out primality_test.f90
実行結果の例:
Please enter an integer n >= 2 : 6700417 6700417 is a prime number.
プログラム例9:更に共配列
共配列の下限を 1 以外の値に設定することもできます。以下は、共下限を 79 に設定したプログラム例です。
特に、組込み関数 this_image の引数の有無と出力の違いに注意してください。
program main implicit none integer :: x[79:*] print *, "Image", this_image(), ":", this_image(x) end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out lower_cobound.f90
実行結果の例:
Image 3 : 81 Image 2 : 80 Image 4 : 82 Image 1 : 79
また、多次元の共配列も可能です。
[ multi_dimensional_coarray.f90 ]
program main implicit none integer :: x[2, 79:*] print *, "Image", this_image(), ":", this_image(x) end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out multi_dimensional_coarray.f90
実行結果の例:
Image 2 : 2 79 Image 1 : 1 79 Image 3 : 1 80 Image 4 : 2 80
プログラム例10:手続と共配列
共配列を手続(関数またはサブルーチン)で使うこともできます。以下は、共配列の仮引数の例です。
subroutine mysubr(n, p, x, y, z, ...) integer :: n, p real :: x[2, p/2, *] ! Scalar real :: y(n)[p, *] ! Explicit shape real :: z(:, :)[2, *] ! Assumed shape ︙
以下は、関数で共配列を用いたプログラム例です。
program main implicit none real, allocatable :: a(:)[:] integer :: i, me, ne me = this_image() ne = num_images() allocate (a(ne)[*]) a = 0 do i = 1, me a(i) = i end do sync all print *, "Image", me, ":", myfunc(a) contains real function myfunc(x) real, intent(in) :: x(:)[*] integer :: i, me, ne me = this_image() ne = num_images() myfunc = 0 do i = 1, ubound(x, 1) if (me == 1) then myfunc = myfunc + x(i)[ne] else myfunc = myfunc + x(i)[me - 1] end if end do end function end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out procedure_coarray.f90
実行結果の例:
Image 3 : 3.0000000 Image 4 : 6.0000000 Image 1 : 10.0000000 Image 2 : 1.0000000
プログラム例11:エラー処理
像制御文に stat 指定子を設定すると、文実行後の像の状態を知ることができ、それに応じた処理を行うことができます。
stat = 状態変数
例えば、sync all 文に stat 指定子を設定した場合、文の実行後に状態変数には以下の値が代入されます。
① 文の実行が成功した場合は、値 0 が代入されます。
② 像の1つが停止状態の場合は、組込みモジュール iso_fortran_env 中の値 stat_stopped_image が代入されます。
③ 上記 ② でなく、かつ、像の1つが失敗状態の場合は、組込みモジュール iso_fortran_env 中の値 stat_failed_image が代入されます。
④ 上記 ② でも ③ でもなく、かつ、その他のエラー条件が発生した場合は、stat_stopped_image および stat_failed_image 以外の処理系依存の正の値が代入されます。
以下は、sync all 文に stat 指定子を設定したプログラム例です。
ここで、stopped_images および failed_images は、それぞれ、停止状態および失敗状態の像の像番号を取得する組込み関数です。
program main use, intrinsic :: iso_fortran_env, only: stat_stopped_image, stat_failed_image implicit none integer :: me, sync_stat me = this_image() if (me == 1) then print *, "Hello image", me else stop end if sync all (stat = sync_stat) if (sync_stat /= 0) then if (sync_stat == stat_stopped_image) then print *, "Stopped images", stopped_images() else if (sync_stat == stat_failed_image) then print *, "Failed images", failed_images() error stop me else print *, "Unexpected failure", sync_stat error stop me end if end if print *, "Goodbye image", me end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out error_handling.f90
実行結果の例:
Hello image 1 Stopped images 2 3 4 Goodbye image 1
プログラム例12:チーム
像の集合を部分集合(チーム)に分割することができます(例えば、像 1、像 3、像 5 ... をチーム 1 とし、像 2、像 4、像 6 ... をチーム 2 とするなど)。そして、各チームを独立した共配列プログラムとして扱うことができます。
チームの形成は、以下のように form team 文を用いて行います。
use, intrinsic :: iso_fortran_env ︙ type (team_type) :: new_team ︙ form team (mod(this_image(), 3) + 1, new_team)
ある像が form team 文を実行すると、form team 文の第 1 引数の値がチーム番号としてその像に割り振られます。
第 2 引数には、組込みモジュール iso_fortran_env 中で定義された派生型 team_type の変数を指定します。
このコード例の場合は、像 3、像 6、像 9 ... はチーム 1 に属し、像 1、像 4、像 7 ... はチーム 2 に属し、像 2、像 5、像 8 ... はチーム 3 に属します。結果として、3つのチームに分けられます。
そして、form team 文で形成したチームは、以下の change team 構文内で有効となります。
change team (new_team) ︙ ! Statements executed with the new_team. end team
例えば、6 個の像を、
チーム 1{像 3、像 5}
チーム 2{像 1、像 2、像 4、像 6}
と、2つのチームに分けたとします。このとき、change team 構文内では、チーム毎に像番号が再付番されます。
チーム 1{像 3 → 像 1、像 5 → 像 2}
チーム 2{像 1 → 像 1、像 2 → 像 2、像 4 → 像 3、像 6 → 像 4}
そして、例えば、像 4 が change team 構文内で以下の組込み関数を呼び出したとき、その戻り値は、
team_number ( ) は 2 (属しているチームのチーム番号)
this_image ( ) は 3 (チーム内で再付番された像番号)
num_images ( ) は 4 (属しているチームの像の個数)
となります。
また、change team 構文内では、この再付番された像番号を用いて、自分が所属するチーム内の像の共配列変数にアクセスすることができます。(他のチームの像の共配列変数にはアクセスできません。)そして、像制御文(同期)もチーム内の像に対して働きます。
従って、この例の場合、チーム 1 は像の個数 2 の共配列プログラムとして、チーム 2 は像の個数 4 の共配列プログラムとして、それぞれ独立にコーディングすることができます。
そこで、以下のような定番のコーディングパターンがあります。
change team (new_team) select case (team_number()) case (1) ︙ ! Code for images in team 1. case (2) ︙ ! Code for images in team 2. end select end team
まず、以下のプログラム例で、組込み関数 team_number、this_image、num_images の change team 構文内での戻り値を確認してみます。
なお、このプログラムでは、4 個の像を、
チーム 1{像 2、像 4}
チーム 2{像 1、像 3}
とチーム分けしています。
[ team_1.f90 ]
program main use, intrinsic :: iso_fortran_env implicit none type (team_type) :: odd_even integer :: me me = this_image() form team (mod(me, 2) + 1, odd_even) change team (odd_even) print *, "Image", this_image(), "/", num_images(), "in team", team_number(), "(", me, ")" end team end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out team_1.f90
実行結果の例:
Image 1 / 2 in team 1 ( 2 ) Image 2 / 2 in team 2 ( 3 ) Image 2 / 2 in team 1 ( 4 ) Image 1 / 2 in team 2 ( 1 )
次のプログラム例の準備として、2つの共配列プログラム【プログラム例3】と【プログラム例4】を以下に再掲します。
【プログラム例3】
[ scalar_coarray.f90 ]
program main implicit none integer :: i, s[*] s = this_image() sync all if (this_image() == 1) then do i = 2, num_images() s = s + s[i] end do print *, "The sum of integers from 1 to", num_images(), "is", s end if end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out scalar_coarray.f90
実行結果の例:
The sum of integers from 1 to 4 is 10
【プログラム例4】
[ array_coarray.f90 ]
program main implicit none integer, parameter :: n = 100 real :: a(n)[*] integer :: i call random_number(a) sync all if (this_image() == 1) then do i = 2, num_images() a = a + a(:)[i] end do print *, "Mean :", sum(a) / (n * num_images()) end if end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out array_coarray.f90
実行結果の例:
Mean : 0.5060082
以下のプログラム例は、case (1)(チーム 1)に上記【プログラム例3】の実行部を、case (2)(チーム 2)に上記【プログラム例4】の実行部を、そのままコピー&ペーストしています。
各チームを独立した共配列プログラムとしてコーディングできることが分かります。
[ team_2.f90 ]
program main use, intrinsic :: iso_fortran_env implicit none integer, parameter :: n = 100 integer :: i, s[*] real :: a(n)[*] type (team_type) :: odd_even form team (mod(this_image(), 2) + 1, odd_even) change team (odd_even) select case (team_number()) case (1) s = this_image() sync all if (this_image() == 1) then do i = 2, num_images() s = s + s[i] end do print *, "The sum of integers from 1 to", num_images(), "is", s end if case (2) call random_number(a) sync all if (this_image() == 1) then do i = 2, num_images() a = a + a(:)[i] end do print *, "Mean :", sum(a) / (n * num_images()) end if end select end team end program
コンパイルコマンドの例:
nagfor -coarray=cosmp -num_images=4 -o a.out team_2.f90
実行結果の例:
The sum of integers from 1 to 2 is 3 Mean : 0.5097195
参考文献
[1] JIS X 3001-1:2023 (ISO/IEC 1539-1:2018) プログラム言語 Fortran — 第1部:基底言語,日本規格協会
[2] Michael Metcalf, John Reid, Malcolm Cohen,“Modern Fortran Explained: Incorporating Fortran 2018”,Oxford University Press