NAG Fortran コンパイラで共配列(Coarray)を使う

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 文を用いたプログラム例です。

[ with_sync_all.f90 ]

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 文を外したバージョンです。

[ without_sync_all.f90 ]

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 は、像の個数を取得する組込み関数です。

[ 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:配列の共配列

スカラと同様に、配列の共配列も利用できます。以下は、実数型配列の共配列の宣言です。

real :: x(10)[*]

また、codimension 属性を用いて以下のように宣言することもできます。

real, dimension(10), codimension[*] :: x

以下では、像 3 の配列要素 x(8) に数値 79 を代入しています。

x(8)[3] = 79

以下では、像 5 の配列要素 x(2) の値が出力されます。

print *, x(2)[5]

以下は、配列の共配列を用いたプログラム例です。

[ 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

プログラム例5:動的な配列の共配列

動的な配列の共配列を利用にするには、allocatable 属性を付加します。

real, allocatable :: x(:)[:]

また、codimension 属性を用いて以下のように宣言することもできます。

real, allocatable, dimension(:), codimension[:] :: x

割付けは以下のように行います。

allocate (x(10)[*])

なお、スコープ外に出ると割付けは自動的に解放されますので、再割付けなどの必要がなければ、deallocate を特に明示する必要はありません。

以下は、上記【プログラム例4】の動的バージョンです。

[ allocatable_coarray.f90 ]

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 の処理は一時停止します。その後、対応する下記 A が実行されると、像 2 の処理は再開します。

A if (this_image() == 3) sync images(2)

逆に、A が先に実行された場合、像 3 の処理は一時停止します。その後、対応する @ が実行されると、像 3 の処理は再開します。

以下は、sync images 文を用いたプログラム例です。このプログラムでは像の処理が、像 1、像 2、... と順番に行われます。

[ sync_images_1.f90 ]

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

また、以下のような定番のコーディングパターンもあります。

[ sync_images_2.f90 ]

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:素朴な素数判定プログラム

[ primality_test.f90 ]

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 の引数の有無と出力の違いに注意してください。

[ lower_cobound.f90 ]

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
  ︙

以下は、関数で共配列を用いたプログラム例です。

[ procedure_coarray.f90 ]

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 が代入されます。

A 像の1つが停止状態の場合は、組込みモジュール iso_fortran_env 中の値 stat_stopped_image が代入されます。

B 上記 A でなく、かつ、像の1つが失敗状態の場合は、組込みモジュール iso_fortran_env 中の値 stat_failed_image が代入されます。

C 上記 A でも B でもなく、かつ、その他のエラー条件が発生した場合は、stat_stopped_image および stat_failed_image 以外の処理系依存の正の値が代入されます。

以下は、sync all 文に stat 指定子を設定したプログラム例です。
ここで、stopped_images および failed_images は、それぞれ、停止状態および失敗状態の像の像番号を取得する組込み関数です。

[ error_handling.f90 ]

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

関連情報

共配列(Coarray)チュートリアル

関連情報
MENU
Privacy Policy  /  Trademarks