###東京大学航空宇宙工学科/専攻 Advent Calendar 2020
この記事は東京大学航空宇宙工学科/専攻 Advent Calendar 2020の12日目の記事として書かれています。航空宇宙工学科/専攻の偉大な先輩方の中で雑な記事を書いてしまい申し訳ない限りです。面白い記事が先輩や同期によって書かれているので是非見ていってください。
######注意
この文章はMPIなど最低限の並列計算の知識があることが前提になっています。
「MPIなにそれ美味しいの? 」という方はまずはMPIの記事を調べることをお勧めします。
また、私はFortranが好きなだけの素人ですので間違いがあっても許してください…。
#はじめに
みなさん、初めまして。航空宇宙工学科B3のFortran大好き人間です。皆さんはFortranと聞くと、「空力の先生が使ってた」か「古代語、使ってる人なんているの」と考えるでしょう。
しかし、Fortranは今でも現役で使われています。 それはスパコンの中で使われています。 Fortranは世界初の高級言語で過去の資産が多く、数値計算が得意なことから今でもスパコンに用いられています。 そのスパコンを動かす際にはMPIなどで各CPUに分散させる必要があります。ただMPIは非常に書くのが面倒です。それを変えようと言うのがPGAS言語です。PGAS言語は分散メモリ型でも共有メモリのように扱えるものです。その中にCo-array Fortranがあります。
今回は、そのCo-arrayについて調べてみました。
#Co-array Fortranとは
Co-array FortranとはFortran2008以降に導入された分散並列機能です。MPIと同様に明示的にデータ分散、計算分散、通信、同期を行うことができます。
MPIとの違いとしては文法として並列計算を行えること、MPIでは双方向通信が基本であったが、Co-arrayでは片方向通信が基本、Co-arrayでは[]というイメージ番号のみを用いた通信のプログラミングなので意識せず並列計算プログラミングが可能になることが挙げられます。
#コンパイラーごとのCo-array Fortranの環境整備
###Intel compiler
Intel® Parallel Studio XE Cluster EditionであればMPI Libraryがついてくるためそのままの環境で使用可能です。
Intel compilerは学生やOSS開発者だと永続ライセンスが無料でもらえますが、for MacだけIntel® Parallel Studio XE Composer Editionとなっており、MPI Libraryが付いてきません。そのためCo-arrayは使用不能なので注意してください。(実際、メイン機がMacの筆者もCo-arrayを使用する際にはLinuxを用いています)
###GFortran(GNU,GCC)
GFortranでCo-arrayを使用するためには、外部ライブラリが必要となります。
一般的な外部ライブラリとしてはOpenCoarraysが挙げられます。
インストールはサイトを確認して行ってください。(読者の使うコンピュータに差がありすぎて、一概述べられませんでした)
#Hello World!
##プログラム
program hello
implicit none
integer::me, p
p = num_images()
me = this_image()
print*,'hello from', me, 'in', p
end program hello
これが基本的なhello worldの書き方です。num_images
によって全てのイメージ数を取得し、this_image
により自分のイメージの番号を取得できます。そのほかは基本的なFortranの記法で書くことができます。
##コンパイル
###Intel compiler
####共有メモリ型の場合
Intel compilerの場合は基本的には-coarray
と-coarray-num-images=
の二つのオプションでコンパイルすることが可能です。(-coarray-num-images=
については環境変数で定めている場合は不要)よって以下のようにコンパイルできます
ifort -coarray=shared -coarray-num-images=64 hello.f90
####分散メモリ型の場合
-coarray
はデフォルトでは=shared
という共有メモリでの実行が規定されているので、分散メモリで使う際には-coarray=distributed
のオプションを使う必要があります。また、分散メモリの場合はバックエンドでMPIを使う必要があるため、MPI実行用のconfigファイルと-coarray-config-file
が必要になります。
-envall -n 64 ./a.out
などとしてください。 -envall
はMPIに環境変数を受け渡すオプションです。
分散メモリの場合は
ifort -coarray=distributed -coarray=config-file=./hello.conf ./hello.f90
とすればコンパイル可能です。-coarray-num-images=
はconfigfileの中で定義しているので不要となります。
#####分散メモリ型における別のコンパイル方法
the Intel® Cluster Toolkit licenseをインストールしている場合は
ifort -coarray=distributed -coarray-num-images=64 hello.f90
でも可能です。
###GFortran(GNU,GCC)
基本的には
caf hello.f90
で十分です。
##実行
###Intel compiler
裏でMPIが走るためいきなり実行ファイルを指定して構いません
./a.out
###GFortran(GNU,GCC)
裏でMPIは勝手に走ってくれないため
cafrun ./a.out
mpirun ./a.out
とする必要があります。
##実行結果
hello from 1 in 64
hello from 4 in 64
などといっぱい出てきます。
#型宣言・通信・制御
##CO配列
CO配列はイメージごとに要素を割り当てる配列です。
integer :: x[5]
のように宣言することができます。上のように宣言しておいて
x[1]=1
とするとイメージ1のxに1が入ります。
x=1
とした場合には全てのイメージのxに1が入ります。もちろんですが
x[1]=1
x[2]=2
とした場合はイメージ1のxには1,イメージ2の2が入ります。
##Co-array Fortranの通信
Co-array Fortranの通信は「片方向通信」です。そのため、相手の状態に関係なく通信を行います。よって、同期待ち、コピー回数の削減、オーバラップが可能になります。しかし、データがちゃんと更新されているかはプログラマが保証する必要があるので注意してください。
###PUT
co配列の要素は自分自身であれば[]なしで参照でき、他のイメージであれば[]で参照することができます。自分自身の要素を他に送る場合には
if(this_image()==1)x[2]=x
とすることででき、意味はイメージ2にイメージ1のxを渡すことができます(制御の部分で上の書き方の注意事項があります)
###GET
上とは反対に自分自身が他のイメージから要素を得る場合には
if(this_image()==2)x=x[1]
とすることででき、意味はイメージ2がイメージ1からxを得るということです。(制御の部分で上の書き方の注意事項があります)
##制御
###this_image
THIS_IMAGE()
この関数を読んだイメージ番号がintegerで返ってきます
###num_images
NUM_IMAGES()
イメージの総数がintegerで返ってきます
###critical
critical
~~~~
end critical
criticalの間にあるブロックは一度に一つのイメージしかそのブロックを実行できなくなります。
また、ブロックの演算が終わるまで、その他のイメージはイメージ制御されないようにしなければなりません。よって、criticalブロック中ではイメージ制御文は動きません
###sync all
sync all
すべてのイメージが同期をとります。
つまり全てのイメージがsync allを実行するまでそれ以降の文の実行は停止されます
####PUTおよびGETの注意
上の例でははx[2]にちゃんとしたx[1]が入ってから通信がおこなわれるか、計算がおこなわれるかが不安定です。そのためsync allを入れる必要があります。
sync all
if(this_image()==1)x[2]=x
sync all
sync all
if(this_image()==2)x=x[1]
sync all
###sync_image
sync_image(integer / *)
sync_imageは()で呼ばれたイメージは他の関数が自分に対してsync_imageをしないとそれ以降の文は実行されません。よって、下のように使います。
if(this_image()==1)then
sync_image(*)
!すべてが揃ってからイメージ1にやって欲しいこと
else
sync_image(1)
end if
###sync memory
~~~ 1番目ににやって欲しいこと
sync memory
~~~ 2番目ににやって欲しいこと
コンパイラで最適化を行うと実行の順番が変わる場合があります。一般には問題ない場合に行われますが、並列計算の場合には問題のある時に行われる場合があります。よって、それを防ぐのがsync memoryです。これを使うことによりsync memoryより前の文がsync memoryより後の分より確実に先に実行されます。
#一般に使うMPIの集団通信の関数のプログラムの例
##Broadcast
Broadcastは
sync all
if(this_image() .ne. 1)x=x[1]
sync all
のように書くことができます。MPIより比較的簡単に書くことができることがわかります。
##Scatter
scatterは
sync all
recv(1:n)=send(1:n,this_image())[1]
sync all
のように書くことができます。
##Gather
Gatherは
sync all
recv(1:n,this_image())[1]=send(1:n)
sync all
のように書くことができます。
##Reduce
sumをとる関数であれば何も考えなければ
sync all
if(this_image() .eq. 1)then
do i=2,num_images()
x=x+x[i]
end do
end if
sync all
のように記述することもできますし、二分木を用いて記述することもここまでお読みの方はできると思います。
また、実際に確認していないのでわかりませんが、
call co_sum(x,xtemp[1])
と書くことができるようです。他にもreduce処理はco_~
で用意されているようです
#プログラムの例
申し訳ありません。時間がなかったのでプログラムを書くことができませんでした。
そのため、いい感じの例のリンクだけ添付しておきます
https://fortran66.hatenablog.com/entry/20120422/1335080600
#Co-array vs MPI
https://www.researchgate.net/publication/221597031_CAF_versus_MPI_-_Applicability_of_Coarray_Fortran_to_a_Flow_Solver
などによるとコア数が少ない時にはMPIの方が良いが、コア数が増えるとその性能差は減るとのこと。
#終わりに
時間の制約から最後の方は駆け足になってしまい申し訳ありませんが、Co-array Fortranをちょっとでも面白そうだなと思っていただければ幸いです。
そして、Fortranも古代語ではなく日々進化していて並列計算をしやすいように頑張っていると知ってもらえればこれほど嬉しいことはありません。
また、Co-arrayに興味を持った方は日本語の資料や解説書は少ないのでPGAS(Co-arrayなどの分散型だがそれを気にせずにかける言語)言語そのものについてや論文を当たることをお勧めします。
MAKE FORTRAN GREAT AGAIN!!
#参考URL
東京大学航空宇宙工学科/専攻 Advent Calendar 2020