940 likes | 1.27k Views
クレイ・ジャパン・インク 200 8 年 5 月. Cray XT4 システム プログラミング 実践編. はじめに. 本資料では、 Cray XT ユーザ向け並列プログラミングを説明します 本資料では、サンプルプログラムで実例を示しながら、基本的な関数の使い方を説明し、数値計算の並列化をユーザ自身が行えるようになることを目指します サンプルプログラムは Fortran を使用しています. Agenda. Cray XT シリーズ 並列プログラミングモデル MPI 簡単な MPI プログラム データ通信1 同期 ブロードキャスト 集積通信
E N D
クレイ・ジャパン・インク2008年5月 Cray XT4システムプログラミング実践編
はじめに • 本資料では、Cray XTユーザ向け並列プログラミングを説明します • 本資料では、サンプルプログラムで実例を示しながら、基本的な関数の使い方を説明し、数値計算の並列化をユーザ自身が行えるようになることを目指します • サンプルプログラムはFortranを使用しています Cray Inc.
Agenda • Cray XTシリーズ • 並列プログラミングモデル • MPI • 簡単なMPIプログラム • データ通信1 • 同期 • ブロードキャスト • 集積通信 • 台形公式数値積分の並列化 • データ通信2 • データ分散と収集 • I/Oの並列化 • アプリケーションの並列化 • さらに詳しく学ぶには • まとめ Cray Inc.
並列プログラミングモデル(アーキテクチャ)並列プログラミングモデル(アーキテクチャ) • 共有メモリ型 • プログラミングが容易(自動並列、OpenMPディレクティブ) • PE間で同期をとり、fork/joinを繰り返す • スケーラビリティが低い • 主に、~数十CPUまでの処理 • 分散メモリ型 • プログラムを明示的に並列化する(MPI, SHMEM, Co-Array/UPC) • 明示的な同期指示がない限りは非同期実行 • スケーラビリティが高い • 主に、数百~数千CPUまでの処理 Cray Inc.
並列プログラミングモデル(ソフトウエア) • SPMD (Single Program Multiple Data)全PEで実行するプログラムは同一、各PEに与えられるデータが異なる • MPMD (Multiple Program Multiple Data)各PEで実行するプログラム、各PEに与えられるデータ、共に異なる Cray Inc.
MPI • MPI(Message Passing Interface)は分散メモリ型並列プログラミングのためのインターコネクト通信の標準API • 分散メモリ型並列プログラミングでは、各PEは独立したメモリ(ローカルメモリ)を持つ • PE間では、お互いの持つデータを通信により交換しながら処理を実現 Cray Inc.
ランクとコミュニケータ • MPIプログラムを実行する各PEはランクとコミュニケータによって一意に特定される • コミュニケータはPEの集合 • 開始時には全PEが属するMPI_COMM_WORLDが定義されている • ユーザは新たなコミュニケータを定義することが可能 • PEにはランクと呼ばれる識別番号が与えられる • ランクは必ず0から連続に番号付けされ、 0~#NPE-1の範囲となる • 同一のコミュニケータ内では、各PEに与えられるランクは重複しない Cray Inc.
MPIプログラムの実行イメージ • 全PEは非同期的にプログラムを実行 • 並列領域で互いにメッセージ交換し、データ整合性を維持 Cray Inc.
MPIプログラムのコンパイルと実行 • Crayプログラミング環境でMPIは統合されている • MPIのリンク指定を明示的にする必要はなし • 標準コンパイルコマンドでそのままMPIプログラムもコンパイル・リンクされる • Fortran: ftn • C: cc • C++: CC • MPIプログラムの実行 aprun -n nprocs ./a.out Cray Inc.
簡単なMPIプログラム(hello.f) 1. 1 program main 2 * 3 include 'mpif.h' 4 * 5 call MPI_Init(ierr) 6 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 7 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 8 * 9 print*,'Hello World from',myrank,' of ',nprocs,'procs.' 10 * 11 call MPI_Finalize(ierr) 12 stop 13 end 3行目: MPI用ヘッダファイルの読み込み 5行目: MPIの初期化 6行目: 全PE数をnprocsに代入 7行目: 自PEのランクをmyrankに代入 11行目: MPIの終了処理 Cray Inc.
MPIの初期化と制御 • ヘッダファイルmpif.hはMPIに関する定数やデータ型を定義している • MPIを使用する全てのサブルーチンでincludeする必要がある • MPIはMPI_Init呼出~MPI_Finalize呼出の範囲で使用可能 • この範囲外での使用は未定義 • PE数とPE ID(ランク)により、処理を制御 • PE数はMPI_Comm_Sizeにより取得 • ランクはMPI_Comm_Rankにより取得 • PE毎に一意に与えられるのはランクのみ • ランクに基づき、IF文で制御するプログラムを作る Cray Inc.
簡単なMPIプログラム(hello.f) 1. 再掲 1 program main 2 * 3 include 'mpif.h' 4 * 5 call MPI_Init(ierr) 6 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 7 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 8 * 9 print*,'Hello World from',myrank,' of ',nprocs,'procs.' 10 * 11 call MPI_Finalize(ierr) 12 stop 13 end 3行目: MPI用ヘッダファイルの読み込み 5行目: MPIの初期化 6行目: 全PE数をnprocsに代入 7行目: 自PEのランクをmyrankに代入 11行目: MPIの終了処理 Cray Inc.
簡単なMPIプログラム(hello.f) 2. hello.fの実行例(4PEの場合) $ ftn hello.f$ aprun -n 4 ./a.out Hello World from 0 of 4 procs. Hello World from 1 of 4 procs. Hello World from 3 of 4 procs. Hello World from 2 of 4 procs.FORTRAN STOPFORTRAN STOPFORTRAN STOPFORTRAN STOP Cray Inc.
プログラム中の時間計測 MPI_Wtime関数: MPIプログラムの経過時間を返す include ’mpif.h’ REAL*8 st,ed REAL*8 elapsed st =MPI_Wtime() * <<SOME WORK HERE>> ed =MPI_Wtime() elapsed=ed-st MPI_Wtimeの粒度に関してはMPI_Wtickでシステムへ問い合わせる Cray Inc.
データ通信 PE間でのメッセージを送受信するための基本関数 MPI_Send(buf,length,type,dest,tag,comm,ierr) MPI_Recv(buf,length,type,src ,tag,comm, istat,ierr) • 送信側、受信側の両者で暗黙的同期が取られる • 通信がデッドロックに陥らないように、注意 Cray Inc.
データ通信における型の指定 • MPIでデータを通信するには、データ型を指定しなければならない • MPIとFortranの基本データ型の対応は下表の通り • ユーザが新たな型を定義することも可能 Cray Inc.
MPIデータ通信プログラム(sendrecv.f) 1. 1 program main 2 * 3 include 'mpif.h' 4 * 5 character*72 msg 6 integer istat(MPI_STATUS_SIZE) 7 * 8 call MPI_Init(ierr) 9 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 10 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 11 * 12 if(myrank .eq. 0) then 13 do id=1,nprocs-1 14 call MPI_Recv(msg,72,MPI_CHARACTER,id,10, 15 & MPI_COMM_WORLD,istat,ierr) 16 print*,msg 17 enddo 18 else 19 write(msg,*) 'NPROCS=',nprocs,':MYRANK=',myrank 20 call MPI_Send(msg,72,MPI_CHARACTER,0,10, 21 & MPI_COMM_WORLD,ierr) 22 endif 23 * 24 call MPI_Finalize(ierr) 25 stop 26 end Cray Inc.
MPIデータ通信プログラム(sendrecv.f) 2. • ランク0のPEが、他のPEからのメッセージを順次表示 • sendには対応するrecvが必要だということに注意 sendrecv.fの実行例(4PEの場合) $ ftn sendrecv.f$ aprun -n 4 ./a.out NPROCS= 4 :MYRANK= 1 NPROCS= 4 :MYRANK= 2 NPROCS= 4 :MYRANK= 3FORTRAN STOPFORTRAN STOPFORTRAN STOPFORTRAN STOP Cray Inc.
同期(barrier.f) 1. コミュニケータ内の全PEが同期をとるための関数 MPI_Barrier(comm,ierr) • MPIプログラムは、各PEで非同期実行 →明示的同期が必要 • コミュニケータに属する全PEが呼出す Cray Inc.
同期(barrier.f) 2. 1 program main 2 * 3 include 'mpif.h' 4 * 5 call MPI_Init(ierr) 6 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 7 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 8 * 9 do i=0,nprocs-1 10 call MPI_Barrier(MPI_COMM_WORLD,ierr) 11 if(myrank .eq. i) 12 & print*,'HelloWorld from',myrank,' of ',nprocs,'procs.' 13 enddo 14 * 15 call MPI_Finalize(ierr) 16 stop 17 end $ ftn barrier.f$ aprun -n 4 ./a.out HelloWorld from 0 of 4 procs. HelloWorld from 1 of 4 procs. HelloWorld from 2 of 4 procs. HelloWorld from 3 of 4 procs.FORTRAN STOPFORTRAN STOPFORTRAN STOPFORTRAN STOP Cray Inc.
同期(barrier.f) 3. Cray Inc.
ブロードキャスト(bcast.f) 1. あるPEが持つデータを同一コミュニケータ内の全PEへ配信(=ブロードキャスト通信)するための関数 MPI_Bcast(buf,count,type,root,comm,ierr) • 暗黙的な同期を含む • コミュニケータ内の全PEが呼出す Cray Inc.
ブロードキャスト(bcast.f) 2. 1 program main 2 * 3 include 'mpif.h' 4 * 5 call MPI_Init(ierr) 6 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 7 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 8 * 9 if(myrank .eq. 0) read(*,*) data 10 * 11 call MPI_Bcast(data,1,MPI_REAL,0,MPI_COMM_WORLD,ierr) 12 * 13 write(*,*) 'MYRANK=',myrank,':DATA=',data 14 * 15 call MPI_Finalize(ierr) 16 stop 17 end $ ftn bcast.f$ aprun -n 4 ./a.out 123456789.0 MYRANK= 0 :DATA= 1.2345679E+09 MYRANK= 1 :DATA= 1.2345679E+09 MYRANK= 2 :DATA= 1.2345679E+09 MYRANK= 3 :DATA= 1.2345679E+09FORTRAN STOPFORTRAN STOPFORTRAN STOPFORTRAN STOP Cray Inc.
ブロードキャスト(bcast.f) 3. $ ftn bcast.f$ aprun -n 4 ./a.out 123456789.0 MYRANK= 0 :DATA= 1.2345679E+09 MYRANK= 1 :DATA= 1.2345679E+09 MYRANK= 2 :DATA= 1.2345679E+09 MYRANK= 3 :DATA= 1.2345679E+09FORTRAN STOPFORTRAN STOPFORTRAN STOPFORTRAN STOP Cray Inc.
集積通信 全PE上に散っているデータの集積演算を行うための関数 MPI_Reduce(send,recv,count,type,op, root,comm,ierr) • 暗黙的な同期を含む • コミュニケータ内の全PEが呼出す Cray Inc.
集積通信における演算の指定 • 集積通信では、加算積算をはじめとする基本的な演算を指定することができる • 指定可能な演算は下表の通り Cray Inc.
集積通信(reduce.f) 1. 1 program main 2 * 3 include 'mpif.h' 4 * 5 dimension array(9) 6 data array/1.,2.,3.,4.,5.,6.,7.,8.,9./ 7 * 8 call MPI_Init(ierr) 9 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 10 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 11 * 12 sum=0.d0 13 do i=(9*myrank)/nprocs+1,(9*(myrank+1))/nprocs 14 sum=sum+array(i) 15 enddo 16 * 17 print*,'MYRANK=',myrank,':SUM=',sum 18 * 19 call MPI_Reduce(sum,tmp,1,MPI_REAL,MPI_SUM,0, 20 & MPI_COMM_WORLD,ierr) 21 * 22 if(myrank .eq. 0) print*,'TMP=',tmp 23 * 24 call MPI_Finalize(ierr) 25 stop 26 end Cray Inc.
$ ftn reduce.f$ aprun -n 3 ./a.out MYRANK= 0 :SUM= 6.000000 MYRANK= 1 :SUM= 15.00000 MYRANK= 2 :SUM= 24.00000 TMP= 45.00000FORTRAN STOPFORTRAN STOPFORTRAN STOP 集積通信(reduce.f) 2. 1 program main 2 * 3 include 'mpif.h' 4 * 5 dimension array(9) 6 data array/1.,2.,3.,4.,5.,6.,7.,8.,9./ 7 * 8 call MPI_Init(ierr) 9 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 10 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 11 * 12 sum=0.d0 13 do i=(9*myrank)/nprocs+1,(9*(myrank+1))/nprocs 14 sum=sum+array(i) 15 enddo 16 * 17 print*,'MYRANK=',myrank,':SUM=',sum 18 * 19 call MPI_Reduce(sum,tmp,1,MPI_REAL,MPI_SUM,0, 20 & MPI_COMM_WORLD,ierr) 21 * 22 if(myrank .eq. 0) print*,'TMP=',tmp 23 * 24 call MPI_Finalize(ierr) 25 stop 26 end Cray Inc.
台形公式数値積分の逐次プログラム • 積分区間[a:b]と分割数dを入力する • 積分区間をd分割し、台形則を適用する • 個々に計算した台形の面積を全て足し合わせ、積分値を求める • 求めた積分値を出力する Cray Inc.
台形公式数値積分 以降では、実用に近いプログラムの並列化の例として、台形公式数値積分のプログラムの並列化を行います Cray Inc.
台形公式数値積分の並列プログラム • ランク0のPEで積分区間[a:b]と分割数nを入力する • 積分区間をn分割し、その中で自分の担当する区間を決定する • 各PEで担当する区間に対して、台形則を適用する • 各PEで担当する区間に対しするローカルな積分値を求める • 各PEで求めたローカルな積分値の総和により、全体の積分値を求める • ランク0のPEで求めた積分値を出力する Cray Inc.
台形公式数値積分の並列プログラム Cray Inc.
台形公式数値積分: mainルーチン 1 program main 2 * 3 include 'mpif.h' 4 * 5 call MPI_Init(ierr) 6 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 7 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 8 * 9 if(myrank .eq. 0) then 10 write(*,*) 'a,b,n?' 11 read(*,*) a,b,n 12 endif 13 * 14 call MPI_Bcast(a,1,MPI_REAL ,0,MPI_COMM_WORLD,ierr) 15 call MPI_Bcast(b,1,MPI_REAL ,0,MPI_COMM_WORLD,ierr) 16 call MPI_Bcast(n,1,MPI_INTEGER,0,MPI_COMM_WORLD,ierr) 17 * 18 call dist_range(a,b,n,a_loc,b_loc,n_loc,nprocs,myrank) 19 * 20 call integral(a_loc,b_loc,n_loc,result_loc) 21 * 22 call MPI_Reduce(result_loc,result,1,MPI_REAL,MPI_SUM,0, 23 & MPI_COMM_WORLD,ierr) 24 * 25 if(myrank .eq. 0) write(*,*) 'A,B,N=',a,b,n 26 if(myrank .eq. 0) write(*,*) 'INTEGRAL=',result 27 * 28 call MPI_Finalize(ierr) 29 stop 30 end Cray Inc.
台形公式数値積分: 領域分割ルーチン dist_range 1 subroutine dist_range(a,b,n,a_loc,b_loc,n_loc,nprocs,myrank) 2 * 3 d=(b-a)/dble(n) 4 * 5 call para_range(1,n,nprocs,myrank,mysta,myend) 6 * 7 a_loc=a+d*mysta 8 b_loc=a+d*myend 9 n_loc=myend-mysta+1 10 * 11 return 12 end 13 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 14 subroutine para_range(n1,n2,nprocs,myrank,ista,iend) 15 * 16 iwk1=(n2-n1+1)/nprocs 17 iwk2=mod(n2-n1+1,nprocs) 18 ista=myrank*iwk1+n1+min(myrank,iwk2) 19 iend=ista+iwk1-1 20 if(myrank .lt. iwk2) iend=iend+1 21 * 22 return 23 end Cray Inc.
台形公式数値積分: 積分ルーチン integral 1 subroutine integral(a,b,n,result) 2 * 3 d=(b-a)/dble(n) 4 * 5 result=0. 6 do i=1,n 7 f1=f(a+d*(i-1.)) 8 f2=f(a+d*(i )) 9 result=result+d*(f1+f2)/2. 10 enddo 11 * 12 return 13 end 14 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 15 function f(x) 16 * 17 f=sin(x) 18 * 19 return 20 end Cray Inc.
台形公式数値積分: 実行結果 • サンプルプログラム通り、sin(x)を被積分関数とし、区間[0:1]で積分すると、厳密解は0.45969769413186…である • 実行結果は厳密解に近い数値となっており、並列処理が正しく行われていることがわかる $ ftn integ.f$ aprun -n 4 ./a.out a,b,n?0.0 1.0 1000000 A,B,N= 0.000000 1.000000 1000000 INTEGRAL= 0.4596627FORTRAN STOPFORTRAN STOPFORTRAN STOPFORTRAN STOP Cray Inc.
sendrecv通信 MPI_Send、MPI_Recvを連続して呼び出す • SeaStar-to-CPUのup/down両リンクを同時に使用するため個別に呼び出すよりも効率が良い MPI_SendRecv(sbuf,slength,stype,dest,stag,rbuf,rlength,rtype,src,rtag, comm,istat,ierr) Cray Inc.
sendrecv通信(sendrecv.f) 1. 1 program main 2 * 3 include 'mpif.h' 4 * 5 character*32 bufs,bufr 6 dimension istat(MPI_STATUS_SIZE) 7 * 8 call MPI_Init(ierr) 9 call MPI_Comm_Size(MPI_COMM_WORLD,nprocs,ierr) 10 call MPI_Comm_Rank(MPI_COMM_WORLD,myrank,ierr) 11 * 12 write(bufs,*) 'Message from rank',myrank 13 * 14 mydst=mod(myrank+nprocs+1,nprocs) 15 mysrc=mod(myrank+nprocs-1,nprocs) 16 call MPI_SendRecv(bufs,32,MPI_CHARACTER,mydst,10, 17 & bufr,32,MPI_CHARACTER,mysrc,10, 18 & MPI_COMM_WORLD,istat,ierr) 19 * 20 print*,myrank,':',bufr 21 * 22 call MPI_Finalize(ierr) 23 stop 24 end Cray Inc.
sendrecv通信(sendrecv.f) 2. sendrecv.fの実行例(4PEの場合) $ ftn sendrecv.f $ aprun –n 4 ./a.out 0 : Message from rank 3 2 : Message from rank 1 3 : Message from rank 2 1 : Message from rank 0 FORTRAN STOPFORTRAN STOPFORTRAN STOPFORTRAN STOP Cray Inc.
非同期通信 1. • MPI_Send / MPI_Recvによる通信では送受信両プロセスが同期することを保障しなければならない →ユーザの負担大 • 通信のリクエストを登録しておき、後はシステムに任せてしまう →同期の必要なし →XTのSeaStarの通信処理とCPUでの計算をオーバーラップ MPI_Isend(buf,length,type,dest,tag,comm, ireq,ierr) MPI_Irecv(buf,length,type,src ,tag,comm, ireq,ierr) MPI_Wait(ireq,istat,ierr) MPI_WaitAll(n,ireqs,istats,ierr) Cray Inc.
非同期通信 2. • MPI_Send / MPI_Recvと同じデータ送受信機能 • 送受信のPEで同期をとらない • リクエストのみ登録し通信が完了する前に制御がCPU側へ戻る • 登録した送受信バッファは通信完了まで書き換えてはならない • 通信完了を保障するにはMPI_Wait / MPI_WaitAllで問い合わせ • 通信と計算のオーバーラップの方法 • MPI_Irecvを発行(受信側バッファの準備) • MPI_Isendを発行(送信側データの登録) • 送受信データを使用しない計算処理 • MPI_Wait / MPI_WaitAllによる通信の完了 これにより2~4の間でCPUの計算処理とSeaSterの通信処理をオーバーラップして、遅延を隠匿できる Cray Inc.