KOBE HPC サマースクール 2019（初級）12. 並列計算性能の評価方法，送受信関数（時間計測関数，バリア同期関数，sendrecv関数）

2019/8/29

KOBE HPC サマースクール 20191

演習12-1 MPI_Allreduceの応用：ベクトルの正規化
𝑛次元ベクトル 𝒙 （double x[N];）の第 𝑖 要素を 𝑖𝑥𝑖𝑖1（𝑖0, ⋯ , 𝑛1 とする．

1）

𝒙 を正規化したベクトル 𝒙⁄ 𝒙
次実行動作を確認せよ．

を求めるプログラム normalize.c の逐変数は倍精度（double）とし，倍精度で計算する． 𝒙である．

は 𝒙 の各要素の2乗和の平方根

正規化されたベクトルの要素は𝑥𝑖𝑖 1 ⁄ 𝑛 𝑛 1 2𝑛 1 /6であるので（簡単に計算できる），プログラムではこの値と比較することによって，正しく計算ができている（Error = 0.0）ことを確認している．
$ cp /tmp/Summer/M‐3/normalize.c ./$ icc –O3 normalize.c$ ./a.out
Error = 0.000000

2019/8/29

KOBE HPC サマースクール 20192

演習12-2 normalize.cを並列化せよ
各プロセスが自分の担当分の要素について，2乗和を計算する．
各プロセスの担当する要素（nprocs はMPIプロセス数）
istart = (n/nprocs)*myrankiend= (n/nprocs)*(myrank+1)‐1

プロセス0

プロセス1

プロセス2

プロセス3

ベクトルの格納方法
各プロセスは長さ n の配列を持ち，そのうち自分の担当部分のみを使う

プロセス0
プロセス0では，ここの部分が使われない

全プロセスの総和を MPI_Allredcue関数で求め，各プロセスはその総和を使って，自分の担当する要素について正規化を行う．
n=1000 としてプロセス数を1，2，4，8と変えて計算せよ．結果の確認も工夫すること．
2019/8/29

KOBE HPC サマースクール 20193時間計測関数，バリア同期関数

2019/8/29

KOBE HPC サマースクール 20194

計算時間の計測
並列計算の目的は，計算時間の短縮にある．
大規模問題を解くためにメモリ容量を増やすことも目的の一つ

同じ結果が得られるが，アルゴリズムや書き方が異なったプログラムのうち，どれが一番良いか？
（正しい結果が得られるならば）計算時間の短いものが良いはず．

計算時間を計測して比較する．
2019/8/29

KOBE HPC サマースクール 20195

計算時間を計測する方法
計測のための変数を倍精度実数型で宣言する．

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;）
2019/8/29

KOBE HPC サマースクール 20196時間計測のイメージ
各プロセスでの計算時間の測定関数
MPI_Wtime()
ある時点を基準とした経過秒数を倍精度実数型で返す関数

プログラムのある区間の計算時間の測定
プログラムの実行は各プロセスで独立なので，開始時間や終了時間が異なる．
ある部分の計算時間の計測では，バリア同期（MPI_Barrier）により測定開始と測定終了の足並みを揃えて，計測する．
バリア同期

バリア同期

経過時間
プロセス0
プロセス1
プロセス2
プロセス3

2019/8/29

KOBE HPC サマースクール 20197

演習12-3 並列化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で動作するグラフ描画ソフトウェア．
2019/8/29

KOBE HPC サマースクール 20198表：ベクトルサイズと並列数の関係
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

24816

2019/8/29KOBE HPC サマースクール 2019

9MPI_Sendrecv関数

2019/8/29

KOBE HPC サマースクール 201910演習12-3 【準備】 関数 MPI_Sendrecv
プログラム sr.c は，２つのプロセスで互いにデータを送りあうプログラムである．
プログラムをコピー，実行し，動作を確かめる．

$ cp /tmp/Summer/M‐3/sr.c ./$ icc sr.c –lmpi$ qsub ...
← ２プロセスのMPIのバッチジョブ$ 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

2019/8/29

KOBE HPC サマースクール 201911

プログラム sr.c の説明#include <mpi.h>
int main( int argc, char **argv ){double a0, a1;
int nprocs, myrank;
MPI_Status status;【省略】
if( myrank == 0 ) {a0 = 1.0 ;a1 = ‐99.0 ;} else {a0 = 2.0 ;a1 = ‐99.0 ;};include行

rank 0では，a0=1.0, a1=0.0

rank 1では，a0=0.0, a1=2.0【省略】※ 交換部分：各プロセスでのsend，recvの実行順序に注意

if( myrank == 0 ) {MPI_Send( &a0, 1, MPI_DOUBLE, 1, 100, MPI_COMM_WORLD );MPI_Recv( &a1, 1, MPI_DOUBLE, 1, 200, MPI_COMM_WORLD, &status );} else {MPI_Recv( &a1, 1, MPI_DOUBLE, 0, 100, MPI_COMM_WORLD, &status );MPI_Send( &a0, 1, MPI_DOUBLE, 0, 200, MPI_COMM_WORLD );
};
【省略】
}

2019/8/29

rank 0の a0 の値を rank 1へ送る．
rank 1からの値を a1 で受け取る．
rank 0 からの値を a1 で受け取る．
rank 1の a0 の値を rank 0へ送る．

※キーワード: ブロッキング関数

KOBE HPC サマースクール 201912双⽅向通信︓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:2019/8/29

送信するデータのための変数名（先頭アドレス）
送信するデータの数
送信するデータの型
MPI_INT, MPI_DOUBLE, MPI_CHAR など送信する相手のプロセス番号（destination）
メッセージ識別番号．送るデータを区別するための番号
受信するデータのための変数名（先頭アドレス）
受信するデータの数（整数型）
受信するデータの型
送信してくる相手のプロセス番号
メッセージ識別番号．送られて来たデータを区別するための番号
コミュニケータ（例えば，MPI_COMM_WORLD）受信の状態を格納する変数
KOBE HPC サマースクール 201913

演習12-4 ：プログラムsr.c の書き換えプログラム sr.c のデータの交換部分を MPI_Sendrecv関数
で書き換えよ．
if( myrank == 0 ) {MPI_Send( &a0, 1, MPI_DOUBLE, 1, 100, MPI_COMM_WORLD );MPI_Recv( &a1, 1, MPI_DOUBLE, 1, 200, MPI_COMM_WORLD, &status );} else {MPI_Recv( &a1, 1, MPI_DOUBLE, 0, 100, MPI_COMM_WORLD, &status );MPI_Send( &a0, 1, MPI_DOUBLE, 0, 200, MPI_COMM_WORLD );};MPI_Sendrecv( ．．．);

2019/8/29

rank 0 では， rank 1 に送り，rank 1 から受け取る．
rank 1 では， rank 0 に送り，rank 0 から受け取る．

KOBE HPC サマースクール 201914

【発展】MPI_Sendrecvの行列への適用
2次元配列（u[N+2][N+2] ）をブロック行分割する．
2次元配列（ブロック行分割）

外側の領域

PE0

PE1

このとき，自分の上下の１行の要素を，隣接するプロセスの持つ領域の外側に受信用の領域を確保し，その領域に各プロセスが転送するプログラムを作る．
2019/8/29

KOBE HPC サマースクール 2019

PE2

PE3

上下のプロセスから1行を受信（受信用の領域を確保しておく）
15

MPI_Sendrecvの発展（続き）

第1の sendrecv

第2の sendrecv

ダミープロセス

ダミープロセス

自分の担当範囲を計算
変数myrankを用いて，担当範囲 js ～ je 行を計算
受信領域を考慮し，js‐1, je+1 行の領域を扱う．

rank 0

メモリ節約の観点では，自分の領域分のメモリがあれば良いが，c言語のポインタ，malloc関数を使ってやる必要があるので，本スクールではやらない（上級）．
rank 1

MPI_Sendrecv による送受信
まず，上側に js 行を送り，下側から送られてくるデー
タを (je+1) 行で受信
rank 2

次に，下側に je 行を送り，上側から送られてくるデー
タを (js‐1) 行に受信
最上下端のプロセスは，ダミープロセス（MPI_PROC_NULL） と送受信するようにする．
MPI_Sendrecv の source, dest に使うことが出来る．ダミープ
ロセスを指定すると，実際にはどこからにもデータを送らないし，どこからもデータを受け取らない．

rank 3

ダミープロセス2019/8/29

KOBE HPC サマースクール 2019

ダミープロセス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;(N+2)次の行列として宣言する．後で分かる．

int nprocs, myrank, upper, lower, srtag;
MPI_Status status;

Recvで必要な配列変数の宣言

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;

2019/8/29（存在しない場合は MPI_PROC_NULL とする）
下側プロセスのプロセス番号を設定

KOBE HPC サマースクール 201917

プログラム sr_matrix.c（続き）
for( i=is; i<=ie; i++ ) {for( j=0; j<N+2; j++ ) {u[i][j] = (myrank+1)*10.0 ;};};/* from the lower process to the upper one */MPI_Sendrecv();/* 下側のプロセスから上側へ */MPI_Sendrecv による送受信/* from the upper process to the lower one */MPI_Sendrecv();/* 上側のプロセスから下側へ */MPI_Sendrecv による送受信

printf("Rank = %d %d %d¥n", myrank, is, ie ) ;for( i=is‐1; i<=ie+1; i++ ) {printf("%6.2f", u[i][N/2] ) ;};printf("¥n") ;N/2 の列のみプリントして確認している．

MPI_Finalize();
return 0;}

2019/8/29

KOBE HPC サマースクール 201918

演習12-5 ：プログラムの完成と実行
sr_matrix.c は未完成である．
MPI_Sendrecvの部分を完成させ，コンパイルして，2，4 プロセスで実行し，データの送受信が正しくできていることを確かめよ.
$cp/tmp/Summer/M‐3/sr_matrix.c./

実行結果（出力順は，以下とは異なる）．
Rank= 0, is:ie=1:50.00 10.00 10.00 10.00 10.00 10.00 20.00
Rank= 1, is:ie=6:10

上のプロセスのie行が，下のプロセスの(is‐1）行にコピー

10.00 20.00 20.00 20.00 20.00 20.00 30.00

下のプロセスのis行が，上のプロセスの(ie+1)行にコピー

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

2019/8/29

0.00

KOBE HPC サマースクール 201919参考：Fortran版

2019/8/29

KOBE HPC サマースクール 201920計算時間を計測する方法
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() ：倍精度実数を返す関数
2019/8/29

KOBE HPC サマースクール 201921双方向通信：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:
2019/8/29

送信するデータのための変数名（先頭アドレス）
送信するデータの数（整数型）
送信するデータの型（MPI_REAL，MPI_INTEGERなど）
送信する相手プロセスのランク番号
メッセージ識別番号．送くるデータを区別するための番号
受信するデータのための変数名（先頭アドレス）
受信するデータの数（整数型）
受信するデータの型（MPI_REAL，MPI_INTEGERなど）
送信してくる相手プロセスのランク番号
メッセージ識別番号．送られて来たデータを区別するための番号
コミュニケータ（例えば，MPI_COMM_WORLD）
受信の状態を格納するサイズMPI_STATUS_SIZEの配列（整数型）
戻りコード（整数型）
KOBE HPC サマースクール 201922

【発展】MPI_Sendrecvの行列への適用
2次元配列がブロック列分割されている．
このとき，自分の両端の１列の要素を，隣接するプロセスの持つ領域の外側に受信用の領域を確保し，その領域にそれぞれ転送するプログラムを作る．
PU0

PU1

PU2

PU3

ブロック列分割

2次元配列

両隣のプロセスから1列を受信（受信用の領域を確保しておく）

2019/8/29

KOBE HPC サマースクール 201923

MPI_sendrecvの応用（続き）
自プロセスの担当範囲を計算
自プロセスの担当範囲は js列 ～ je列

mpi_sendrecv による送受信
まず，右隣に je 列を送り，左隣から js‐1 列に受信
次に，左隣に js 列を送り，右隣から je+1 列に受信両端のプロセスは，ダミープロセス（MPI_PROC_NULL) と送受信するようにする．
MPI_Sendrecv の source, dest に使うことが出来る．ダミープロセスを指定すると，実際にはどこからにもデータを送らないし，どこからもデータを受け取らない．

第1の sendrecv

第2の sendrecv

2019/8/29

KOBE HPC サマースクール 201924

プログラム M-8（続く）
program sr_matrix
use mpi
implicit none
integer, parameter :: N=20
real(8), dimension(0:N+1,0:N+1) :: uinteger :: i, j, js, je
integer :: nprocs, myrank, left, right, srtag, ierrinteger, dimension(MPI_STATUS_SIZE) :: status

Recvで必要な配列変数の宣言

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) left = MPI_PROC_NULL
if( myrank == 0right = myrank+1
if( myrank == nprocs‐1 ) right = MPI_PROC_NULL

左右のプロセスのプロセス番号を計算（存在しない場合は MPI_PROC_NULL とする）

do i = 0, N+1
do j = 0, N+1u(i,j) = 0.0
end doend do2019/8/29

KOBE HPC サマースクール 201925

プログラム M-8（続き）
do j = js, je
do i = 0, n+1u(i,j) = (myrank+1)*10.0
end doend do! From the left process to the right one
call mpi_sendrecv( ... )mpi_sendrecv による送受信! From the right process to the left one
call mpi_sendrecv( ... )print '("Rank=",i2," js:je=",i4,":",i4)', myrank, js, je
print '(10f6.2)', (u(N/2,j),j=js‐1,je+1)

正しく受信できたことを確認

call mpi_finalize( ierr )end program sr_matrix

2019/8/29

KOBE HPC サマースクール 201926

演習12-5F ：プログラムの完成と実行
sr_matrix.f90 は未完成である．
MPI_Sendrecvの部分を完成させ，コンパイルして，2，4 プロセスで実行し，データの送受信が正しくできていることを確かめよ.
$cp/tmp/Summer/M‐3/sr_matrix.f90./

実行結果（出力順は，以下とは異なる）．
Rank= 0, js:je=

1:

50.00 10.00 10.00 10.00 10.00 10.00 20.00
Rank= 1, js:je=

6:10

左のプロセスのie列が，右のプロセスの(is‐1）列にコピー

10.00 20.00 20.00 20.00 20.00 20.00 30.00

右のプロセスのis列が，下のプロセスの(ie+1)列にコピー

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

2019/8/29

0.00

KOBE HPC サマースクール 201927