581 Views
November 21, 23
スライド概要
R-CCS 計算科学研究推進室
KOBE HPC サマースクール 2023(初級) 10. 並列計算性能の評価方法 (時間計測関数,バリア同期関数,sendrecv関数) 2023/9/21 KOBE HPCサマースクール 2023 1
時間計測関数,バリア同期関数 2023/9/21 KOBE HPCサマースクール 2023 2
計算時間の計測 並列計算の目的は,計算時間の短縮にある. 大規模問題を解くためにメモリ容量を増やすことも目的の一つ 同じ結果が得られるが,アルゴリズムや書き方が異なったプログラムのうち,ど れが一番良いか? (正しい結果が得られるならば)計算時間の短いものが良いはず. 計算時間を計測して比較する. 2023/9/21 KOBE HPCサマースクール 2023 3
計算時間を計測する方法 double time0, time2; ・ ・ MPI_Barrier( MPI_COMM_WORLD ); time0 = MPI_Wtime(); 計測のための変数を倍精度実数型で宣言する. MPI_Barrier 関数で,計測開始の足並みを揃える. MPI_Wtime 関数で開始時刻を time0 に設定 (計測する部分) MPI_Barrier( MPI_COMM_WORLD ); time1 = MPI_Wtime(); 全プロセスで終了の足並みを揃える. MPI_Wtime 関数で終了時刻を time1 に設定 (time1-time0 を出力する) time1-time0 が計測した部分の計算時間となる. MPI_Barrier(comm) : バリア同期関数 comm: コミュニケータ(例えば,MPI_COMM_WORLD) var = MPI_Wtime() :倍精度実数を返す関数(double var;) 2023/9/21 KOBE HPCサマースクール 2023 4
時間計測のイメージ 各プロセスでの計算時間の測定関数 MPI_Wtime() ある時点を基準とした経過秒数を倍精度実数型で返す関数 プログラムのある区間の計算時間の測定 プログラムの実行は各プロセスで独立なので,開始時間や終了時間が異なる. ある部分の計算時間の計測では,バリア同期(MPI_Barrier)により測定開始と測定終了の足並みを揃え て,計測する. バリア同期 バリア同期 経過時間 プロセス0 プロセス1 プロセス2 プロセス3 2023/9/21 KOBE HPCサマースクール 2023 5
演習10-1 並列化normalize.c の実行時間計測 並列化したベクトルの正規化プログラムの実行時間を計ってみる. 計測範囲は,各プロセスが二乗和を求めるところの直前から,正規化した直後まで,とする. n = 50,000,100,000,500,000 に対して,並列数を1,2,4,8,16 と変化させ て時間を計測せよ. 1プロセスの実行時間を T(1),mプロセスの実行時間を T(m) とするとき,速度向上率 T(1)/T(m) を求めよ(次の表参照). また,速度向上率のグラフを描け(例えば gnuplot を使う). Gnuplot は,Linux で動作するグラフ描画ソフトウェア. Rokkoサーバ上では,「module load gnuplot」で環境設定後に利用可能. 2023/9/21 KOBE HPCサマースクール 2023 6
表:ベクトルサイズと並列数の関係 n=50,000 並列数 (m) 1 計算時間(秒) T(m) n=100,000 速度向上率 T(1)/T(m) 計算時間(秒) T(m) n=500,000 速度向上率 T(1)/T(m) 1.000 1.000 計算時間(秒) T(m) 速度向上率 T(1)/T(m) 1.000 2 4 8 16 2023/9/21 KOBE HPCサマースクール 2023 7
MPI_Sendrecv関数 2023/9/21 KOBE HPCサマースクール 2023 8
演習10-2 【準備】 関数 MPI_Sendrecv プログラム srseparate.c は,2つのプロセスで互いにデータを送りあ うプログラムである. プログラムをコピー,実行し,動作を確かめる. $ cp /home/guest60/share/srseparate.c ./ $ icc srseparate.c –lmpi $ qsub ... ← 2プロセスの MPI のバッチジョブ(jobm.sh を適宜修正して使用) $ cat xxxxx.onnnnnn ← バッチジョブの実行結果を確認 Before exchange... Rank: 0, a0= 1.0, a1= -99.0 Before exchange... Rank: 1, a0= 2.0, a1= -99.0 After exchange... Rank: 0, a0= 1.0, a1= 2.0 After exchange... Rank: 1, a0= 2.0, a1= 1.0 2023/9/21 KOBE HPCサマースクール 2023 9
プログラム srseparate.c の説明
#include <stdio.h>
#include <mpi.h>
#define N 100
double a0[N], a1[N];
int main( int argc, char **argv )
{
int nprocs, myrank, i;
【省略】
if( myrank == 0 ) {
for( i = 0; i < N; i++ ){
a0[i] = 1.0;
a1[i] = -99.0;}
} else {
for( i = 0; i < N; i++ ){
a0[i] = 2.0;
a1[i] = -99.0;}
}
【省略】
if( myrank == 0 ) {
MPI_Send( &a0[0], N, MPI_DOUBLE,
MPI_Recv( &a1[0], N, MPI_DOUBLE,
} else {
MPI_Recv( &a1[0], N, MPI_DOUBLE,
MPI_Send( &a0[0], N, MPI_DOUBLE,
}
【省略】
}
2023/9/21
include行
rank 0では,a0=1.0, a1=-99.0
rank 1では,a0=2.0, a1=-99.0
※ 交換部分:各プロセスでのsend,recvの実行順序に
注意
1, 100, MPI_COMM_WORLD );
1, 200, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
rank 0 の a0 の値を rank 1 へ送る.
rank 1 からの値を a1 で受け取る.
0, 100, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
0, 200, MPI_COMM_WORLD );
rank 0 からの値を a1 で受け取る.
rank 1 の a0 の値を rank 0 へ送る.
※キーワード: ブロッキング関数
KOBE HPCサマースクール 2023
10
双方向通信:MPI_Sendrecv関数 MPI_Sendrecv( void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, int recvtag, MPI_Comm comm, MPI_Status *status ) sendbuf: sendcount: sendtype: dest: sendtag: recvbuf: recvcount: recvtype: source: recvtag: comm: status: 2023/9/21 送信するデータのための変数名(先頭アドレス) 送信するデータの数 送信するデータの型 MPI_INT, MPI_DOUBLE, MPI_CHAR など 送信する相手のプロセス番号(destination) メッセージ識別番号.送るデータを区別するための番号 受信するデータのための変数名(先頭アドレス) 受信するデータの数(整数型) 受信するデータの型 送信してくる相手のプロセス番号 メッセージ識別番号.送られて来たデータを区別するための番号 コミュニケータ(例えば,MPI_COMM_WORLD) 受信の状態を格納する変数 KOBE HPCサマースクール 2023 11
演習10-3 :プログラム sendrecv.c プログラム srseparate.c のデータの交換部分を MPI_Sendrecv関数で書き換えた 「プログラム sendrecv.c」を作成せよ. if( myrank == 0 ) { MPI_Send( &a0[0], MPI_Recv( &a1[0], } else { MPI_Recv( &a1[0], MPI_Send( &a0[0], }; N, MPI_DOUBLE, 1, 100, MPI_COMM_WORLD ); N, MPI_DOUBLE, 1, 200, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); N, MPI_DOUBLE, 0, 100, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); N, MPI_DOUBLE, 0, 200, MPI_COMM_WORLD ); MPI_Sendrecv( ...); 2023/9/21 rank 0 では, rank 1 に送り,rank 1 から受け取る. rank 1 では, rank 0 に送り,rank 0 から受け取る. KOBE HPCサマースクール 2023 12
追加演習10-4 n個の MPIプロセスが,それぞれ rank番号×10 の値を持っている. MPIプロセスがリング状につながっているとする. この時, rank #0 -> #1 -> #2 -> ..... -> #n-1 -> #0 と値をぐるっとに回すプログラムを作成せよ. 実行前: 0, 10, 20, 30, 実行後: 10(n-1), 0, 10, 20, 2023/9/21 ....., 10(n-2), 10(n-1) ....., 10(n-3), 10(n-2) KOBE HPCサマースクール 2023 13
追加演習10-4(続き) 前ページのような通信を,リング通信と呼ぶことがある. 初回は,全員が右隣に送信し,左隣から受信する. P0 P1 P2 P3 2回目は,全員が左隣に送信し,右隣から受信する. P0 P1 P2 P3 集団通信関数の内部実装に使用されることもある基本的な通信方式 2023/9/21 KOBE HPCサマースクール 2023 14
【発展】MPI_Sendrecv の行列への適用 2次元配列(u[N+2][N+2] )をブロック行分割する. 2次元配列 (ブロック行分割) 外側の領域 PE0 PE1 PE2 このとき,自分の上下の1行の要素を,隣接するプ ロセスの持つ領域の外側に受信用の領域を確保し, その領域に各プロセスが転送するプログラムを作る. PE3 上下のプロセスから1行を受信 (受信用の領域を確保しておく) 2023/9/21 KOBE HPCサマースクール 2023 15
MPI_Sendrecvの発展(続き) 第1の sendrecv 第2の sendrecv ダミー プロセス ダミー プロセス 自分の担当範囲を計算 変数 myrank を用いて,担当範囲 is ~ ie 行を計算 受信領域を考慮し,is-1, ie+1 行の領域に拡大する. rank 0 メモリ節約の観点では,自分の領域分のメモリがあれば良いが,c言語のポインタ, malloc関数を使ってやる必要があるので,本スクールではやらない(上級). rank 1 MPI_Sendrecv による送受信 まず,上側に is 行を送り,下側から送られてくるデータを (ie+1) 行 で受信 rank 2 次に,下側に ie 行を送り,上側から送られてくるデータを (is-1) 行 に受信 最上下端のプロセスは,ダミープロセス(MPI_PROC_NULL) と送受信するようにする. MPI_Sendrecv の source, dest に使うことが出来る.ダミープロセスを指定すると, 実際にはどこからにもデータを送らないし,どこからもデータを受け取らない. rank 3 ダミー プロセス 2023/9/21 KOBE HPCサマースクール 2023 ダミー プロセス 16
プログラム sr_matrix.c(続く)
#include <stdio.h>
#include <mpi.h>
N = 20 とする
#define N 20
int main( int argc, char **argv )
{
double u[N+2][N+2];
int i, j, is, ie;
int
(N+2)次の行列として宣言する.後で分かる.
nprocs, myrank, upper, lower, srtag;
MPI_Init( &argc, &argv );
MPI_Comm_size( MPI_COMM_WORLD, &nprocs );
MPI_Comm_rank( MPI_COMM_WORLD, &myrank );
is = (N/nprocs)* myrank + 1;
ie = (N/nprocs)*(myrank+1);
各プロセスの担当する行の範囲を計算
upper = myrank-1;
if( myrank == 0
上側プロセスのプロセス番号を設定
) upper = MPI_PROC_NULL;
lower = myrank+1 ;
if( myrank == nprocs-1 ) lower = MPI_PROC_NULL;
2023/9/21
(存在しない場合は MPI_PROC_NULL とする)
下側プロセスのプロセス番号を設定
KOBE HPCサマースクール 2023
17
プログラム sr_matrix.c(続き)
for( i=is; i<=ie; i++ ) {
for( j=0; j<N+2; j++ ) {
u[i][j] = (myrank+1)*10.0 ;
};
};
/* upward data circulation */
MPI_Sendrecv(
);
/* 下側のプロセスから上側へ */
MPI_Sendrecv による送受信
/* downward data circulation */
MPI_Sendrecv(
);
/* 上側のプロセスから下側へ */
MPI_Sendrecv による送受信
for( j=0; j<nprocs; j++) {
if( myrank == j ) {
printf("Rank=%2d, is:ie=%2d:%2d¥n", myrank, is, ie );
for( i=is-1; i<=ie+1; i++ ) {
printf(“%6.2f”, u[i][N/2] ) ;
N/2 の列のみプリントして確認している.
}
printf(“¥n”);
fflush(stdout);
}
MPI_Barrier( MPI_COMM_WORLD );
}
MPI_Finalize();
return 0;
}
2023/9/21
KOBE HPCサマースクール 2023
18
高速化のTips メモリ上のデータは連続的にアクセスする方が速い 以下のコードより… do i = 1, n do j = 1, n c(i,j) = a(i,j) + b(i,j) end do end do for(j=0; i<n; i++) { for(i=0; j<n; j++) { c[i][j] = a[i][j] + b[i][j]; } } 以下のコードの方が高速に処理できる do j = 1, n do i = 1, n c(i,j) = a(i,j) + b(i,j) end do end do for(i=0; i<n; i++) { for(j=0; j<n; j++) { c[i][j] = a[i][j] + b[i][j]; } } ※ C言語の2次元配列のルールは「row-major order」 . ※ プログラミング言語により,メモリ配置のルールは異なるので注意(例:Fortran言語では 「column-major order」… Cとは逆) 2023/9/21 KOBE HPCサマースクール 2023 19
行列のメモリ上の配置 Fortranの場合 (column-major order) Cの場合 (row-major order) j → j → i ↓ 2023/9/21 a[i][j] i ↓ KOBE HPCサマースクール 2023 A(i,j) 20
演習10-5 : プログラムの完成と実行 sr_matrix.c は未完成である. MPI_Sendrecv の部分を完成させ,コンパイルして,2,4 プロセスで実行し,データの送受信が 正しくできていることを確かめよ. $ cp /home/guest60/share/sr_matrix.c ./ 実行結果 Rank= 0, is:ie= 1: 5 0.00 10.00 10.00 10.00 10.00 10.00 20.00 Rank= 1, is:ie= 6: 10 上のプロセスの ie 行が,下のプロセスの (is-1)行にコピー 下のプロセスの is 行が,上のプロセスの (ie+1) 行にコピー されていることを確認する. 10.00 20.00 20.00 20.00 20.00 20.00 30.00 Rank= 2, is:ie= 11: 15 20.00 30.00 30.00 30.00 30.00 30.00 40.00 Rank= 3, is:ie= 16: 20 30.00 40.00 40.00 40.00 40.00 40.00 2023/9/21 0.00 KOBE HPCサマースクール 2023 21
参考:Fortran版 2023/9/21 KOBE HPCサマースクール 2023 22
計算時間を計測する方法 real(DP) :: time0, time2 ・ ・ call mpi_barrier( MPI_COMM_WORLD, ierr ) time0 = mpi_wtime() 計測のための変数を倍精度実数型で宣言する. mpi_barrier 関数で,計測開始の足並みを揃える. mpi_wtime 関数で開始時刻を time0 に設定 (計測する部分) call mpi_barrier( MPI_COMM_WORLD, ierr ) time1 = mpi_wtime() 全プロセスで終了の足並みを揃える. mpi_wtime 関数で終了時刻を time1 に設定 (time1-time0 を出力する) time1-time0 が計測した部分の計算時間となる. mpi_barrier(comm, ierr) : バリア同期関数 comm: コミュニケータ(例えば,MPI_COMM_WORLD) ierr: 戻りコード(整数型) var = mpi_wtime() :倍精度実数を返す関数 2023/9/21 KOBE HPCサマースクール 2023 23
双方向通信:mpi_sendrecv関数 mpi_sendrecv( sendbuff, sendcount, sendtype, dest, sendtag, recvbuff, recvcount, recvtype, source, recvtag, comm, status, ierr ) sendbuff: sendcount: sendtype: dest: sendtag : recvbuff: recvcount: recvtype: source: recvtag: comm: status: ierr: 2023/9/21 送信するデータのための変数名(先頭アドレス) 送信するデータの数(整数型) 送信するデータの型(MPI_REAL,MPI_INTEGERなど) 送信する相手プロセスのランク番号 メッセージ識別番号.送くるデータを区別するための番号 受信するデータのための変数名(先頭アドレス) 受信するデータの数(整数型) 受信するデータの型(MPI_REAL,MPI_INTEGERなど) 送信してくる相手プロセスのランク番号 メッセージ識別番号.送られて来たデータを区別するための番号 コミュニケータ(例えば,MPI_COMM_WORLD) 受信の状態を格納するサイズ MPI_STATUS_SIZE の配列(整数型) 戻りコード(整数型) KOBE HPCサマースクール 2023 24
【発展】MPI_Sendrecv の行列への適用 2次元配列がブロック列分割されている. このとき,自分の両端の1列の要素を,隣接するプロセスの持つ領域の外側に受 信用の領域を確保し,その領域にそれぞれ転送するプログラムを作る. PU0 PU1 PU2 PU3 ブロック列分割 2次元配列 両隣のプロセスから1列を受信 (受信用の領域を確保しておく) 2023/9/21 KOBE HPCサマースクール 2023 25
MPI_sendrecvの応用(続き) 自プロセスの担当範囲を計算 自プロセスの担当範囲は js列 ~ je列 mpi_sendrecv による送受信 まず,右隣に je 列を送り,左隣から js-1 列に受信 次に,左隣に js 列を送り,右隣から je+1 列に受信 両端のプロセスは,ダミープロセス(MPI_PROC_NULL) と送受信するようにする. MPI_Sendrecv の source, dest に使うことが出来る.ダミープロセスを指定すると,実際にはどこからにもデー タを送らないし,どこからもデータを受け取らない. 第1の sendrecv 第2の sendrecv 2023/9/21 KOBE HPCサマースクール 2023 26
プログラム sr_matrix.f90(続く) program sr_matrix use mpi implicit none integer, parameter :: N=20 double precision :: u(0:N+1,0:N+1) integer :: i, j, js, je integer :: nprocs, myrank, left, right, srtag, ierr call mpi_init( ierr ) call mpi_comm_size( MPI_COMM_WORLD, nprocs, ierr ) call mpi_comm_rank( MPI_COMM_WORLD, myrank, ierr ) js = (N/nprocs)* myrank + 1 je = (N/nprocs)*(myrank+1) 各プロセスの担当する列の範囲を計算 left = myrank-1 if( myrank == 0 ) left = MPI_PROC_NULL right = myrank+1 if( myrank == nprocs-1 ) right = MPI_PROC_NULL 左右のプロセスのプロセス番号を計算 (存在しない場合は MPI_PROC_NULL とする) u(0:N+1,0:N+1) = 0.0 2023/9/21 KOBE HPCサマースクール 2023 27
プログラム sr_matrix.f90 (続き)
u(0:N+1,js:je) = (myrank+1)*10.0
mpi_sendrecv による送受信
! rightward data circulation
call mpi_sendrecv( ... )
! leftward data circulation
call mpi_sendrecv( ... )
do i=0,nprocs-1
if( myrank == i ) then
print '("Rank=",i2," js:je=",i4,":",i4)', myrank, js, je
print '(10f6.2)', (u(N/2,j),j=js-1,je+1)
flush(6)
end if
call MPI_Barrier( MPI_COMM_WORLD, ierr )
end do
正しく受信できたことを確認
call mpi_finalize( ierr )
end program sr_matrix
2023/9/21
KOBE HPCサマースクール 2023
28
演習10-5F :プログラムの完成と実行 sr_matrix.f90 は未完成である. MPI_Sendrecv の部分を完成させ,コンパイルして,2,4 プロセスで実行し,データの送受信が 正しくできていることを確かめよ. $ cp /home/guest60/share/sr_matrix.f90 ./ 実行結果 Rank= 0, js:je= 1: 5 0.00 10.00 10.00 10.00 10.00 10.00 20.00 Rank= 1, js:je= 6: 10 左のプロセスの ie 列が,右のプロセスの (is-1) 列にコピー 右のプロセスの is 列が,下のプロセスの (ie+1) 列にコピー されていることを確認する. 10.00 20.00 20.00 20.00 20.00 20.00 30.00 Rank= 2, js:je= 11: 15 20.00 30.00 30.00 30.00 30.00 30.00 40.00 Rank= 3, js:je= 16: 20 30.00 40.00 40.00 40.00 40.00 40.00 2023/9/21 0.00 KOBE HPCサマースクール 2023 29