---
title: KOBE HPC サマースクール（初級）2023 講義11
tags:  #並列計算 #バリア同期  
author: [R-CCS　計算科学研究推進室](https://convert.docswell.com/user/2300203199)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/YJ9NVX5PJ3.jpg?width=480
description: KOBE HPC サマースクール（初級）2023 講義11 by R-CCS　計算科学研究推進室
published: November 21, 23
canonical: https://convert.docswell.com/s/2300203199/5RXJEJ-2023-11-21-155337
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/YJ9NVX5PJ3.jpg)

KOBE HPC サマースクール 2023（初級）
10. 並列計算性能の評価方法
（時間計測関数，バリア同期関数，sendrecv関数）
2023/9/21
KOBE HPCサマースクール 2023
1


# Page. 2

![Page Image](https://bcdn.docswell.com/page/GJ8KP2YXED.jpg)

時間計測関数，バリア同期関数
2023/9/21
KOBE HPCサマースクール 2023
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/LJLZ124N7R.jpg)

計算時間の計測
並列計算の目的は，計算時間の短縮にある．
大規模問題を解くためにメモリ容量を増やすことも目的の一つ
同じ結果が得られるが，アルゴリズムや書き方が異なったプログラムのうち，ど
れが一番良いか？
（正しい結果が得られるならば）計算時間の短いものが良いはず．
計算時間を計測して比較する．
2023/9/21
KOBE HPCサマースクール 2023
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/47MVW8KQ7W.jpg)

計算時間を計測する方法
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


# Page. 5

![Page Image](https://bcdn.docswell.com/page/P7R2Y5XKJ9.jpg)

時間計測のイメージ
各プロセスでの計算時間の測定関数
MPI_Wtime()
ある時点を基準とした経過秒数を倍精度実数型で返す関数
プログラムのある区間の計算時間の測定
プログラムの実行は各プロセスで独立なので，開始時間や終了時間が異なる．
ある部分の計算時間の計測では，バリア同期（MPI_Barrier）により測定開始と測定終了の足並みを揃え
て，計測する．
バリア同期
バリア同期
経過時間
プロセス0
プロセス1
プロセス2
プロセス3
2023/9/21
KOBE HPCサマースクール 2023
5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/PJXDGK65EX.jpg)

演習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


# Page. 7

![Page Image](https://bcdn.docswell.com/page/3JKX451RED.jpg)

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


# Page. 8

![Page Image](https://bcdn.docswell.com/page/LE35LKQGJ5.jpg)

MPI_Sendrecv関数
2023/9/21
KOBE HPCサマースクール 2023
8


# Page. 9

![Page Image](https://bcdn.docswell.com/page/8EDDN39NEG.jpg)

演習10-2 【準備】 関数 MPI_Sendrecv
プログラム srseparate.c は，２つのプロセスで互いにデータを送りあ
うプログラムである．
プログラムをコピー，実行し，動作を確かめる．
$ cp /home/guest60/share/srseparate.c ./
$ icc srseparate.c –lmpi
$ qsub ...
← ２プロセスの 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


# Page. 10

![Page Image](https://bcdn.docswell.com/page/V7PX54ZNE8.jpg)

プログラム srseparate.c の説明
#include &lt;stdio.h&gt;
#include &lt;mpi.h&gt;
#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 &lt; N; i++ ){
a0[i] = 1.0;
a1[i] = -99.0;}
} else {
for( i = 0; i &lt; N; i++ ){
a0[i] = 2.0;
a1[i] = -99.0;}
}
【省略】
if( myrank == 0 ) {
MPI_Send( &amp;a0[0], N, MPI_DOUBLE,
MPI_Recv( &amp;a1[0], N, MPI_DOUBLE,
} else {
MPI_Recv( &amp;a1[0], N, MPI_DOUBLE,
MPI_Send( &amp;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


# Page. 11

![Page Image](https://bcdn.docswell.com/page/2JVLPXWY7Q.jpg)

双方向通信：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


# Page. 12

![Page Image](https://bcdn.docswell.com/page/5EGXPVDWJL.jpg)

演習10-3 ：プログラム sendrecv.c
プログラム srseparate.c のデータの交換部分を MPI_Sendrecv関数で書き換えた
「プログラム sendrecv.c」を作成せよ．
if( myrank == 0 ) {
MPI_Send( &amp;a0[0],
MPI_Recv( &amp;a1[0],
} else {
MPI_Recv( &amp;a1[0],
MPI_Send( &amp;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


# Page. 13

![Page Image](https://bcdn.docswell.com/page/4JQ8261Q7P.jpg)

追加演習10-4
n個の MPIプロセスが，それぞれ rank番号×10 の値を持っている．
MPIプロセスがリング状につながっているとする．
この時，
rank #0 -&gt; #1 -&gt; #2 -&gt; ..... -&gt; #n-1 -&gt; #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


# Page. 14

![Page Image](https://bcdn.docswell.com/page/K742L4RYJ1.jpg)

追加演習10-4（続き）
前ページのような通信を，リング通信と呼ぶことがある．
初回は，全員が右隣に送信し，左隣から受信する．
P0
P1
P2
P3
2回目は，全員が左隣に送信し，右隣から受信する．
P0
P1
P2
P3
集団通信関数の内部実装に使用されることもある基本的な通信方式
2023/9/21
KOBE HPCサマースクール 2023
14


# Page. 15

![Page Image](https://bcdn.docswell.com/page/LJ1ML4QNEG.jpg)

【発展】MPI_Sendrecv の行列への適用
2次元配列（u[N+2][N+2] ）をブロック行分割する．
2次元配列
（ブロック行分割）
外側の領域
PE0
PE1
PE2
このとき，自分の上下の１行の要素を，隣接するプ
ロセスの持つ領域の外側に受信用の領域を確保し，
その領域に各プロセスが転送するプログラムを作る．
PE3
上下のプロセスから1行を受信
（受信用の領域を確保しておく）
2023/9/21
KOBE HPCサマースクール 2023
15


# Page. 16

![Page Image](https://bcdn.docswell.com/page/GJW3VXLMJ2.jpg)

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


# Page. 17

![Page Image](https://bcdn.docswell.com/page/4EZV46MMJ3.jpg)

プログラム sr_matrix.c（続く）
#include &lt;stdio.h&gt;
#include &lt;mpi.h&gt;
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( &amp;argc, &amp;argv );
MPI_Comm_size( MPI_COMM_WORLD, &amp;nprocs );
MPI_Comm_rank( MPI_COMM_WORLD, &amp;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


# Page. 18

![Page Image](https://bcdn.docswell.com/page/Y76532N5EV.jpg)

プログラム sr_matrix.c（続き）
for( i=is; i&lt;=ie; i++ ) {
for( j=0; j&lt;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&lt;nprocs; j++) {
if( myrank == j ) {
printf(&quot;Rank=%2d, is:ie=%2d:%2d¥n&quot;, myrank, is, ie );
for( i=is-1; i&lt;=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


# Page. 19

![Page Image](https://bcdn.docswell.com/page/G75YL25G74.jpg)

高速化の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&lt;n; i++) {
for(i=0; j&lt;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&lt;n; i++) {
for(j=0; j&lt;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


# Page. 20

![Page Image](https://bcdn.docswell.com/page/9J2GL4NDJR.jpg)

行列のメモリ上の配置
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


# Page. 21

![Page Image](https://bcdn.docswell.com/page/DEYVQMPMEM.jpg)

演習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


# Page. 22

![Page Image](https://bcdn.docswell.com/page/VJNMKW5378.jpg)

参考：Fortran版
2023/9/21
KOBE HPCサマースクール 2023
22


# Page. 23

![Page Image](https://bcdn.docswell.com/page/YE9NVXYPE3.jpg)

計算時間を計測する方法
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


# Page. 24

![Page Image](https://bcdn.docswell.com/page/GE8KP26X7D.jpg)

双方向通信：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


# Page. 25

![Page Image](https://bcdn.docswell.com/page/LELZ129NJR.jpg)

【発展】MPI_Sendrecv の行列への適用
2次元配列がブロック列分割されている．
このとき，自分の両端の１列の要素を，隣接するプロセスの持つ領域の外側に受
信用の領域を確保し，その領域にそれぞれ転送するプログラムを作る．
PU0
PU1
PU2
PU3
ブロック列分割
2次元配列
両隣のプロセスから1列を受信
（受信用の領域を確保しておく）
2023/9/21
KOBE HPCサマースクール 2023
25


# Page. 26

![Page Image](https://bcdn.docswell.com/page/4JMVW8ZQJW.jpg)

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


# Page. 27

![Page Image](https://bcdn.docswell.com/page/PJR2Y56KE9.jpg)

プログラム 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


# Page. 28

![Page Image](https://bcdn.docswell.com/page/PEXDGKM57X.jpg)

プログラム 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 &#039;(&quot;Rank=&quot;,i2,&quot; js:je=&quot;,i4,&quot;:&quot;,i4)&#039;, myrank, js, je
print &#039;(10f6.2)&#039;, (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


# Page. 29

![Page Image](https://bcdn.docswell.com/page/3EKX45GR7D.jpg)

演習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


