計算科学入門第7回 MPIによる並列計算

計算科学入門 第 7 回
MPI による並列計算
京都大学 大学院情報学研究科 数理工学専攻/情報教育推進センター
關戸 啓人
計算科学入門 第 7 回
(2013 年 05 月 29 日)
–
/ 72
MPI とは (概要)
MPI とは Message Passing Interface の略で,プロセス間のメッセージ
交換ライブラリの規格.
お互いにメモリに直接触れないプロセスが明示的にデータを他のプロ
セスに渡す,もしくは,受け取る.
MPI の機能は関数として提供される.
コンパイル時に MPI のライブラリにリンクさせる.実際には,リンク
オプションを含んだコンパイラのラッパーが提供される.
OpenMP は何だったか
標準化された,コンパイラの拡張機能.
データの受け渡しは,メモリに直接書き込み,それを読み込む.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
1
/ 72
プロセス並列 (MPI)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
2
/ 72
スレッド並列 (OpenMP)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
3
/ 72
ハイブリッド並列 (MPI + OpenMP)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
4
/ 72
MPI とは (基礎的な諸注意 1)
MPI では 1 つのソースコードを書き,それを複数のプロセスで実行する.
プロセスごとに異なった処理を行う場合は,自分のプロセス番号を調
べて,それを用いる.
OpenMP と違い,1 行 pragma を書けば自動的に並列化してくれるな
どということはない.単純な for ループの並列化も,自分のプロセス
番号や,全体のプロセス数を用いて,各プロセスが担当する範囲を指
定しなければならない.
データ転送も明示的に書かなければならなく,OpenMP と比べて,ソー
スコードが長く,複雑になる傾向がある.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
5
/ 72
MPI とは (基礎的な諸注意 2)
分散メモリ(メモリは共有ではない)
例えば,OpenMP では,変数 int n を宣言したら,全スレッドが同じ
メモリ領域に int n を確保する.あるスレッドが n の値を書き換えれ
ば,別のスレッドの変数 n も書き換えられる.
一方,MPI では,変数 int n を宣言したら,それぞれのプロセスが別々
のメモリ領域に int n を確保する.あるプロセスが n の値を書き換え
ても,別のスレッドの変数 n には影響しない.
少し複雑なことをやろうと思ったときは,MPI の方が気を使うべきこ
とが少ないと思われる.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
6
/ 72
MPI とは (歴史)
MPI 以前(∼1980 年代)
並列計算機ベンダーが各々並列言語もしくは通信ライブラリを作成
MPI-1(1994 年 6 月)
一対一通信
集団通信
(派生データ型,コミュニケータ,・
・
・)
MPI-2(1997 年 7 月)
MPI-3(2012 年 9 月)
参考:MPI の規格書は http://www.mpi-forum.org/docs/docs.html
から閲覧可能
計算科学入門 第 7 回
(2013 年 05 月 29 日)
7
/ 72
実行方法 (Intel MPI の場合)
京大のスパコンでは,C 言語,C++,Fortran で利用可能
コンパイル方法(プログラムが code.***で実行ファイルとして
hoge を作る場合)
C の場合: mpiicc -o hoge code.c
C++の場合: mpiicpc -o hoge code.cpp
Fortran の場合: mpiifort -o hoge code.f
実行方法(8 プロセスで実行する場合)
mpiexec.hydra -n 8 ./hoge
参考:http://web.kudpc.kyoto-u.ac.jp/manual/ja/library
/intelmpi
計算科学入門 第 7 回
(2013 年 05 月 29 日)
8
/ 72
実行方法 (OpenMPI の場合)
以下のどれかの module を load する
module load openmpi/1.6_intel-12.1
module load pgi と module load openmpi/1.6_pgi-12.3
module load openmpi/1.6_gnu-4.4.6
コンパイル
C の場合: mpicc -o hoge code.c
C++の場合: mpic++ -o hoge code.cpp
Fortran 77 の場合: mpif77 -o hoge code.f
Fortran 90 の場合: mpif90 -o hoge code.f90
実行方法(8 プロセスで実行する場合)
mpiexec -n 8 ./hoge
参考:http://web.kudpc.kyoto-u.ac.jp/manual/ja/library/openmpi
計算科学入門 第 7 回
(2013 年 05 月 29 日)
9
/ 72
C 言語と Fortran の違い
これ以降,主に,C 言語での MPI について解説する.違いはあまり無いが,
注意が必要と思われるときは Fortran についても述べる.両方述べるときは,
C 言語は赤,Fortran は青で書く.主な違いは以下の通り.
インクルードするヘッダファイル
C 言語では,mpi.h
Fortran では,mpif.h
関数の引数
Fortran では,引数の最後にエラーコードの戻り値(ierr)を指定す
る事が多い.C 言語では関数の戻り値で取得.
MPI_INIT の引数は違う(後述)
C++では,時々関数の引数や戻り値が異なるが本講義では説明しない.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
10
/ 72
必要最小限の関数
本当に必要最小限の関数
MPI_INIT : MPI 環境の開始
MPI_FINALIZE : MPI 環境の終了
MPI_COMM_SIZE : 全プロセス数を調べる
MPI_COMM_RANK : 自分のプロセス番号を調べる
一対一通信の必要最小限の関数
MPI_SEND : データを送る
MPI_RECV : データを受け取る
計算科学入門 第 7 回
(2013 年 05 月 29 日)
11
/ 72
コミュニケータ
コミュニケータは,いくつかのプロセスからなるグループである
集団通信では,とあるコミュニケータに属すプロセス間でデータのや
り取りをすることができる
最初は,全てのプロセスは MPI_COMM_WORLD というコミュニケータ
に属す
この授業では,使用するコミュニケータは,MPI_COMM_WORLD だけで十
分事足りる.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
12
/ 72
MPI INIT
MPI_INIT は MPI の各関数を呼び出す前に,1 度だけ呼び出されなければ
ならない.また,MPI_INIT を呼び出す前には,C では mpi.h,Fortran では
mpif.h が include されていなければならない.
int MPI_Init(int *argc, char ***argv)
引数は,それぞれ main 関数の引数へのポインタ.
MPI_INIT(IERROR)
INTEGER IERROR には,エラーコードが代入される.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
13
/ 72
MPI FINALIZE
MPI_FINALIZE は(MPI_INIT を呼び出したなら),プログラムが終了す
る前に 1 度だけ呼び出されなければならない.また,MPI_FINALIZE が呼び
出された後は,MPI の関数を使用することはできない.
int MPI_Finalize(void)
MPI_FINALIZE(IERROR)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
14
/ 72
例 ex1.c (4 プロセスで実行)
01
02
03
04
05
06
07
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv){
puts("Hi");
return 0;
}
実行結果
Hi
Hi
Hi
Hi
スライド中の例のコードは,
http://www-is.amp.i.kyoto-u.ac.jp/lab/sekido/に
example7.zip として置いてある.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
15
/ 72
例 ex2.c (4 プロセスで実行)
01
02
03
04
05
06
07
08
09
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv){
MPI_Init(&argc, &argv);
puts("Hi");
MPI_Finalize();
return 0;
}
実行結果
Hi
Hi
Hi
Hi
計算科学入門 第 7 回
(2013 年 05 月 29 日)
16
/ 72
例 ex3.c (4 プロセスで実行)
01
02
03
04
05
06
07
08
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv){
puts("Hi");
MPI_Finalize();
return 0;
}
実行結果
Abort
計算科学入門 第 7 回
(2013 年 05 月 29 日)
17
/ 72
例 ex4.c (4 プロセスで実行)
01
02
03
04
05
06
07
08
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv){
MPI_Init(&argc, &argv);
puts("Hi");
return 0;
}
実行結果
Abort
計算科学入門 第 7 回
(2013 年 05 月 29 日)
18
/ 72
例 ex5.c (4 プロセスで実行)
01
02
03
04
05
06
07
08
09
10
11
12
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv){
MPI_Init(&argc, &argv);
puts("Hi");
MPI_Finalize();
MPI_Init(&argc, &argv);
puts("Hi");
MPI_Finalize();
return 0;
}
実行結果
Abort
計算科学入門 第 7 回
(2013 年 05 月 29 日)
19
/ 72
MPI COMM SIZE
MPI_COMM_SIZE はコミュニケータに属するプロセス数を求める.コミュ
ニケータに MPI_COMM_WORLD を指定すれば,全プロセス数を求めることが
できる.
int MPI_Comm_size(MPI_Comm comm, int *size)
MPI_Comm comm は指定するコミュニケータ
size にプロセス数が代入される
MPI_COMM_SIZE(COMM, SIZE, IERROR)
INTEGER COMM:Fortran では INTEGER.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
20
/ 72
MPI COMM RANK
MPI_COMM_RANK は自分のプロセスの指定したコミュニケータ内での番号
を求める.最も小さい番号は 0 から始まり,連続した整数で記述される.
int MPI_Comm_rank(MPI_Comm comm, int *rank)
MPI_Comm comm は指定するコミュニケータ
rank にプロセス番号が代入される
MPI_COMM_RANK(COMM, RANK, IERROR)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
21
/ 72
例 ex6.c (4 プロセスで実行)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv){
int my_rank, num_proc;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &num_proc);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
printf("Hi, my rank = %d, and size = %d\n", my_rank, num_proc);
MPI_Finalize();
return 0;
}
実行結果
Hi, my
Hi, my
Hi, my
Hi, my
rank
rank
rank
rank
=
=
=
=
1,
2,
3,
0,
計算科学入門 第 7 回
and
and
and
and
size
size
size
size
=
=
=
=
4
4
4
4
(2013 年 05 月 29 日)
22
/ 72
例 ex6.f (4 プロセスで実行)
01
02
03
04
05
06
07
08
実行結果
Hi, my
Hi, my
Hi, my
Hi, my
include "mpif.h"
INTEGER ierr, my_rank, num_proc
call MPI_INIT(ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, num_proc, ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, my_rank, ierr)
write(*,*) ’Hi, my rank = ’, my_rank, ’, and size = ’, num_proc
call MPI_FINALIZE(ierr)
end
rank
rank
rank
rank
=
=
=
=
3
0
1
2
,
,
,
,
and
and
and
and
size
size
size
size
=
=
=
=
4
4
4
4
これらの例 (ex6.*) がすべての基本.出力の順番は実行の度に変わる.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
23
/ 72
MPI SEND
MPI_SEND は一対一通信でデータを送信する関数である.送信先でデータ
が受信されるまで,この関数は終わらないことが多い.
int MPI_Send(void *buf, int count,
MPI_Datatype type, int dest, int tag, MPI_Comm comm)
MPI_SEND(BUF, COUNT, TYPE, DEST, TAG, COMM, IERROR)
INTEGER TYPE は Fortran では整数
void *buf は送信するデータの最初のアドレスを指定する
int count は送信するデータの個数を指定する
MPI_Datatype type は送信するデータの型(後述)を指定する
int dest は受け取るプロセスの番号を指定する
int tag はデータを識別するための番号.受信の時(MPI_RECV)に送信時につけ
た tag の番号を指定する.
MPI_Comm comm はコミュニケータを指定する.
計算科学入門
第7回
(2013 年 05 月 29 日)
24
/ 72
MPI RECV
MPI_RECV は一対一通信でデータを受信する関数である.データを受信す
るまで,この関数は終わらない.RECV は Recieve の意味.
int MPI_Recv(void *buf, int count, MPI_Datatype type,
int source, int tag, MPI_Comm comm, MPI_Status *status)
MPI_RECV(BUF, COUNT, TYPE, SOURCE, TAG, COMM, STATUS, IERROR)
INTEGER STATUS(MPI_STATUS_SIZE) は Fortran では整数の配列
void *buf は受信するデータを格納する最初のアドレスを指定する
int count は受信するデータの個数を指定する
MPI_Datatype type は送信するデータの型(後述)を指定する
int source は送信するプロセスの番号を指定する
int tag はデータを識別するための番号.送信の時(MPI_SEND 等)に指定した tag
と同じ番号を指定する.
MPI_Comm comm はコミュニケータを指定する.
MPI_Status *status は送信に関する情報が代入される.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
25
/ 72
*buf についての補足
C 言語のポインタ,配列に詳しくない人は以下を参考にせよ.
1 つの変数 int a を送信,受信する場合
MPI_Send(&a, 1, MPI_INT, dest, tag, MPI_COMM_WORLD);
配列 int a[10] の最初の 5 要素(a[0]∼a[4])を送信,受信する場合
MPI_Send(a, 5, MPI_INT, dest, tag, MPI_COMM_WORLD);
または,
MPI_Send(&a[0], 5, MPI_INT, dest, tag, MPI_COMM_WORLD);
配列 int a[10] の 6 要素(a[3]∼a[8])を送信,受信する場合
MPI_Send(a+3, 6, MPI_INT, dest, tag, MPI_COMM_WORLD);
または,
MPI_Send(&a[3], 6, MPI_INT, dest, tag, MPI_COMM_WORLD);
計算科学入門 第 7 回
(2013 年 05 月 29 日)
26
/ 72
MPI Datatypes (C 言語)
MPI_SEND,MPI_RECV 等で用いる MPI_Datatype type と C 言語の型
との対応関係は以下の通り.
char : MPI_CHAR
short : MPI_SHORT
int : MPI_INT
long : MPI_LONG
float : MPI_FLOAT
double : MPI_DOUBLE
unsigned char : MPI_UNSIGNED_CHAR
unsigned short : MPI_UNSIGNED_SHORT
unsigned : MPI_UNSIGNED
unsigned long : MPI_UNSIGNED_LONG
long double : MPI_LONG_DOUBLE
等
計算科学入門 第 7 回
(2013 年 05 月 29 日)
27
/ 72
MPI Datatypes (Fortran)
MPI_SEND,MPI_RECV 等で用いる INTEGER TYPE と Fortran の型との対
応関係は以下の通り.
INTEGER : MPI_INTEGER
REAL : MPI_REAL
REAL*8 : MPI_REAL8
DOUBLE PRECISION : MPI_DOUBLE_PRECISION
COMPLEX : MPI_COMPLEX
LOGICAL : MPI_LOGICAL
CHARACTER : MPI_CHARACTER
等
計算科学入門 第 7 回
(2013 年 05 月 29 日)
28
/ 72
(補足)MPI Status
MPI_RECV で,MPI_Status *status には,送信元の rank や,tag など
が代入される.MPI_WAIT_ALL などではエラー情報も代入されることもある
が,通常は関数がエラー情報を返すので,MPI_Status *status のエラー
情報は更新されないことも多い.そのような情報が必要ない場合は,
MPI_Status *status に MPI_STATUS_IGNORE を指定すれば良い.
MPI_SEND で指定する送信データの個数 int count は,MPI_RECV で指定
する受信データの個数 int count より小さくても良い.実際に受信したデー
タの個数を取得するには,MPI_GET_COUNT を用いる.これにも,
MPI_Status *status が必要.
(最後の補足に例がある)
計算科学入門
第7回
(2013 年 05 月 29 日)
29
/ 72
(補足)MPI GET COUNT
MPI_GET_COUNT は受信したデータの個数(バイト数ではない)を取得す
る関数である.
int MPI_Get_count(MPI_Status *status, MPI_Datatype type,
int *count)
MPI_RECV(STATUS, TYPE, COUNT, IERROR)
MPI_Status *status は受信の時に代入されたステータスのポインタを指定する.
int count には受信したデータの個数が代入される.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
30
/ 72
例 ex7.c (一部抜粋,2 プロセスで実行)
04 #define N 3
08
int i, send[N], recv[N], source, dest;
09
MPI_Status status;
14
15
send[0] = 1;
16
for(i=1;i<N;i++) send[i] = (send[i-1] * 3 + my_rank) % 10007;
17
18
source = dest = 1 - my_rank;
19
MPI_Send(send, N, MPI_INT, dest, 0, MPI_COMM_WORLD);
20
MPI_Recv(recv, N, MPI_INT, source, 0, MPI_COMM_WORLD, &status);
実行結果
正常終了(出力は無し)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
31
/ 72
例 ex8.c (一部抜粋,2 プロセスで実行)
04 #define N 3333
08
int i, send[N], recv[N], source, dest;
09
MPI_Status status;
14
15
send[0] = 1;
16
for(i=1;i<N;i++) send[i] = (send[i-1] * 3 + my_rank) % 10007;
17
18
source = dest = 1 - my_rank;
19
MPI_Send(send, N, MPI_INT, dest, 0, MPI_COMM_WORLD);
20
MPI_Recv(recv, N, MPI_INT, source, 0, MPI_COMM_WORLD, &status);
実行結果
終了しない.
(このコードを実行するときは Ctrl-C などで強制終了させよ)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
32
/ 72
デッドロック
先程の例では,
プロセス 0 は,
プロセス 1 にデータを送信してから,
プロセス 1 からデータを受信する.
プロセス 1 は,
プロセス 0 にデータを送信してから,
プロセス 0 からデータを受信する.
この場合,データを送信するとき,互いが互いの受信待ちをする場合がある.
このように,全てのプロセスが,他のプロセスを待つ状態に入り,プログラ
ムが進まなくなることをデッドロックと言う.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
33
/ 72
デッドロック (解決策の例)
デッドロックを回避するには,例えば,
プロセス 0 は,
プロセス 1 にデータを送信してから,
プロセス 1 からデータを受信する.
プロセス 1 は,
プロセス 0 からデータを受信してから.
プロセス 0 にデータを送信する
とすれば,デッドロックに陥らない.または,例えば,後に紹介する
MPI_SENDRECV や非ブロック通信を使っても良い.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
34
/ 72
MPI SENDRECV
MPI_SEND_RECV は一対一通信でデータを互いに送受信する関数である.
int 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)
MPI_SENDRECV(SENDBUF, SENDCOUNT, SENDTYPE, DEST, SENDTAG,
RECVBUF, RECVCOUNT, RECVTYPE, SOURCE, RECVTAG, COMM,
STATUS, IERROR)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
35
/ 72
非ブロック通信
非ブロック通信で送受信する関数は(MPI_ISEND,MPI_IRECV 等)デー
タを送信・受信できたかどうかに関わらず,すぐ終わる.データ通信の間に計
算を進めることができ,高速な計算が可能だが,コードは複雑になりやすい.
まだ送信していないデータを書き換えない,まだ受信していないデータを使
わないように注意.
送受信
MPI_ISEND : 一対一の非ブロック通信で,データの送信
MPI_IRECV : 一対一の非ブロック通信で,データの受信
送受信を終了待ちするための関数
MPI_WAIT : 指定した非ブロック通信が終わるまで待つ
計算科学入門 第 7 回
(2013 年 05 月 29 日)
36
/ 72
MPI ISEND
MPI_ISEND は一対一の非ブロック通信でデータを送信する関数である.送
信先でデータが受信されかどうかに関わらず,この関数は終わる.ISEND の
I は Immediate の意味.
int MPI_Isend(void *buf, int count, MPI_Datatype type,
int dest, int tag, MPI_Comm comm, MPI_Request *request)
MPI_ISEND(BUF, COUNT, TYPE, DEST, TAG, COMM, REQUEST, IERROR)
INTEGER REQUEST は Fortran では整数
MPI_Request *request はこの送信(一般的にはこの関数呼び出し)を識別する
ためのデータが代入される.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
37
/ 72
MPI IRECV
MPI_IRECV は一対一の非ブロック通信でデータを送信する関数である.デー
タを受信しなくても,この関数は終わる.
int MPI_Irecv(void *buf, int count, MPI_Datatype type,
int source, int tag, MPI_Comm comm, MPI_Request *request)
MPI_IRECV(BUF, COUNT, TYPE, SOURCE, TAG, COMM, REQUEST, IERROR)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
38
/ 72
MPI WAIT
MPI_WAIT は指定した非ブロック通信が終了するまで待つ関数である.
int MPI_Wait(MPI_Request *request, MPI_Status *status)
MPI_WAIT(REQUEST, STATUS, IERROR)
MPI_Request *request は待ちたい送受信の際に得られた値を指定する.
MPI_Status *status はステータスが代入される.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
39
/ 72
例 ex9.c (一部抜粋,2 プロセスで実行)
06
07
08
14
15
16
17
18
19
20
21
22
23
24
25
26
int i, source=0, dest=1, tag;
double a = 500.0, b = 0.05, c = 0.0;
MPI_Request request;
if(my_rank == 0){
tag = 1;
MPI_Isend(&a, 1, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD, &request);
tag = 0;
MPI_Isend(&b, 1, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD, &request);
} else if(my_rank == 1){
tag = 0;
MPI_Irecv(&c, 1, MPI_DOUBLE, source, tag, MPI_COMM_WORLD, &request);
printf("recieved %f\n", c);
tag = 1;
MPI_Irecv(&c, 1, MPI_DOUBLE, source, tag, MPI_COMM_WORLD, &request);
printf("recieved %f\n", c);
}
実行結果
recieved 0.000000
recieved 0.000000
計算科学入門 第 7 回
(2013 年 05 月 29 日)
40
/ 72
例 ex10.c (一部抜粋,2 プロセスで実行)
06
int i, source=0, dest=1, tag;
07
double a = 500.0, b = 0.05, c = 0.0;
08
MPI_Request request;
09
MPI_Status status;
15
if(my_rank == 0){
16
tag = 1;
17
MPI_Isend(&a, 1, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD, &request);
18
tag = 0;
19
MPI_Isend(&b, 1, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD, &request);
20
} else if(my_rank == 1){
21
tag = 0;
22
MPI_Irecv(&c, 1, MPI_DOUBLE, source, tag, MPI_COMM_WORLD, &request);
23
MPI_Wait(&request, &status);
24
printf("recieved %f\n", c);
25
tag = 1;
26
MPI_Irecv(&c, 1, MPI_DOUBLE, source, tag, MPI_COMM_WORLD, &request);
27
MPI_Wait(&request, &status);
28
printf("recieved %f\n", c);
実行結果
recieved 0.050000
recieved 500.000000
計算科学入門 第 7 回
(2013 年 05 月 29 日)
41
/ 72
集団通信 (1)
集団通信関数は,あるコミュニケータに属す全てのプロセスが係る通信を 1
回の呼び出しで行うことのできる関数である.処理ごとに最適化されており,
一対一通信で愚直に書くより速いことが多い.また,一対一通信で書くより
もデッドロックなども起きにくい.
集団通信では,基本的に,通信に参加する全てのプロセスが関数を呼び出す.
一対多通信
MPI_BCAST:ある rank の持つデータを全てのプロセスに送受信する
MPI_SCATTER:ある rank の持つデータを,等分して,各々のプロセ
スに配分する
計算科学入門 第 7 回
(2013 年 05 月 29 日)
42
/ 72
集団通信 (2)
多対一通信
MPI_GATHER:MPI_SCATTER の逆.各々のプロセスのデータを連結
して 1 つのプロセスに集める.
MPI_REDUCE:MPI_GATHER と演算を組み合わせたもの.例えば,各々
のプロセスのデータの和,積,最大値,最小値などを求め,1 つのプロ
セスに渡す.
多対多通信
MPI_ALLTOALL:各プロセスの配列を各プロセスに配分する
MPI_ALLGATHER:MPI_GATHER の結果を全プロセスに渡す.
MPI_ALLREDUCE:MPI_REDUCE の結果を全プロセスに渡す.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
43
/ 72
MPI BCAST
MPI_BCAST は集団通信で,ある rank の持つデータを全てのプロセスに送
受信する関数である.送受信に関わる全てのプロセスが MPI_BCAST を呼びだ
さなければならなく,root,comm は一致しなければならない.count,type
も普通は一致する.BCAST は Broadcast の意味.
int MPI_Bcast(void *buf, int count, MPI_Datatype type,
int root, MPI_Comm comm)
MPI_BCAST(BUF, COUNT, TYPE, ROOT, COMM)
void *buf は,送信する,もしくは,受信する,データの最初のアドレス.
int root は,データの送信主のプロセス番号を指定する.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
44
/ 72
MPI BCAST
プロセス 0
プロセス 1
プロセス 2
プロセス 3
a0
b0
c0
d0
a1
b1
c1
d1
a2
b2
c2
d2
a3
b3
c3
d3
...
...
...
...
a2
b2
c2
d2
a3
b3
c3
d3
...
...
...
...
↓
プロセス 0
プロセス 1
プロセス 2
プロセス 3
a0
a0
a0
a0
a1
a1
a1
a1
計算科学入門 第 7 回
(2013 年 05 月 29 日)
45
/ 72
例 ex15.c (一部抜粋,4 プロセスで実行)
06
12
13
14
16
18
19
21
int i, arr[10], sum;
if(my_rank==0){
for(i=0;i<10;i++) arr[i] = i;
}
MPI_Bcast(arr, 10, MPI_INT, 0, MPI_COMM_WORLD);
sum = 0;
for(i=0;i<10;i++) sum += arr[i];
printf("my_rank = %d, sum = %d\n", my_rank, sum);
実行結果
my_rank
my_rank
my_rank
my_rank
=
=
=
=
0,
3,
1,
2,
sum
sum
sum
sum
=
=
=
=
45
45
45
45
計算科学入門 第 7 回
(2013 年 05 月 29 日)
46
/ 72
MPI SCATTER
MPI_SCATTER はある rank の持つデータを,等分して,各々のプロセスに
配分する関数である.
int MPI_Scatter(void *sendbuf, int sendcount,
MPI_Datatype sendtype, void *recvbuf, int recvcount,
MPI_Datatype recvtype, int root, MPI_Comm Comm)
MPI_SCATTER(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF, RECVCOUNT,
RECVTYPE, ROOT, COMM, IERROR)
void *sendbuf は送信するデータの最初のアドレスを指定する.int root で指
定されたプロセス以外では何を指定しても良い.
int sendcount は,1 プロセスあたりに送信するデータの数を指定する.通常,
int recvcount と同じになる.
void *recvbuf は受信するデータの最初のアドレスを指定する.rank が root の
プロセスは void *sendbuf と領域が重なってはいけない.rank が root のプロセス
で,自分には送受信したくない場合は MPI_IN_PLACE(MPI-2 の機能)を指定する.
int root は,データの送信主のプロセス番号を指定する.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
/ 72
47
MPI SCATTER
プロセス 0 (送信)
プロセス 0 (受信)
プロセス 1
プロセス 2
プロセス 3
z0
a0
b0
c0
d0
z1
a1
b1
c1
d1
z2
a2
b2
c2
d2
z3
a3
b3
c3
d3
...
...
...
...
...
z1
a1
a1
a1
a1
z2
a2
b2
c2
d2
z3
a3
b3
c3
d3
...
...
...
...
...
↓
プロセス 0 (送信)
プロセス 0 (受信)
プロセス 1
プロセス 2
プロセス 3
計算科学入門 第 7 回
z0
z0
z1
z2
z3
(2013 年 05 月 29 日)
48
/ 72
例 ex16.c (一部抜粋,4 プロセスで実行)
06
12
13
14
16
18
19
21
int i, arr[12], myarr[3], sum;
if(my_rank==0){
for(i=0;i<3*num_proc;i++) arr[i] = i;
}
MPI_Scatter(arr, 3, MPI_INT, myarr, 3, MPI_INT, 0, MPI_COMM_WORLD);
sum = 0;
for(i=0;i<3;i++) sum += myarr[i];
printf("my_rank = %d, sum = %d\n", my_rank, sum);
実行結果
my_rank
my_rank
my_rank
my_rank
=
=
=
=
3,
0,
1,
2,
sum
sum
sum
sum
=
=
=
=
30
3
12
21
計算科学入門 第 7 回
(2013 年 05 月 29 日)
49
/ 72
例 ex17.c (一部抜粋,4 プロセスで実行)
06
12
13
14
15
16
17
18
19
20
21
22
23
24
int i, arr[12], sum;
if(my_rank==0){
for(i=0;i<3*num_proc;i++) arr[i] = i;
}
if(my_rank == 0)
MPI_Scatter(arr, 3, MPI_INT, MPI_IN_PLACE, 3, MPI_INT, 0,
MPI_COMM_WORLD);
else
MPI_Scatter(arr, 3, MPI_INT, arr, 3, MPI_INT, 0, MPI_COMM_WORLD);
sum = 0;
for(i=0;i<3;i++) sum += arr[i];
printf("my_rank = %d, sum = %d\n", my_rank, sum);
実行結果
my_rank
my_rank
my_rank
my_rank
=
=
=
=
2,
3,
0,
1,
sum
sum
sum
sum
計算科学入門 第 7 回
=
=
=
=
21
30
3
12
(2013 年 05 月 29 日)
50
/ 72
MPI GATHER
MPI_GATHER は各プロセスの持つ同じ長さのデータを結合して,あるプロ
セスに渡す関数である.MPI_SCATTER と逆の動作をする.
int MPI_Gather(void *sendbuf, int sendcount,
MPI_Datatype sendtype, void *recvbuf, int recvcount,
MPI_Datatype recvtype, int root, MPI_Comm Comm)
MPI_GATHER(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF, RECVCOUNT,
RECVTYPE, ROOT, COMM, IERROR)
void *recvbuf は受信するデータの最初のアドレスを指定する.int root で指
定されたプロセス以外では何を指定しても良い.
int recvcount は,1 プロセスあたりの送信するデータの数を指定する.通常,
int sendcount と同じになる.
void *sendbuf は MPI_IN_PLACE を指定可能.
int root は,データを受信するプロセス番号を指定する.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
51
/ 72
MPI GATHER
プロセス 0 (受信)
プロセス 0 (送信)
プロセス 1
プロセス 2
プロセス 3
z0
a0
b0
c0
d0
z1
a1
b1
c1
d1
z2
a2
b2
c2
d2
z3
a3
b3
c3
d3
...
...
...
...
...
b0
a1
b1
c1
d1
c0
a2
b2
c2
d2
d0
a3
b3
c3
d3
...
...
...
...
...
↓
プロセス 0 (受信)
プロセス 0 (送信)
プロセス 1
プロセス 2
プロセス 3
計算科学入門 第 7 回
a0
a0
b0
c0
d0
(2013 年 05 月 29 日)
52
/ 72
MPI REDUCE
MPI_REDUCE は,各プロセスの持つ同じ長さのデータを何らかの演算をし
て,その結果をあるプロセスに渡す関数である.
int MPI_Reduce(void *sendbuf, void *recvbuf, int count,
MPI_Datatype type, MPI_Op op, int root, MPI_Comm comm)
MPI_REDUCE(SENDBUF, RECVBUF, COUNT, TYPE, OP, ROOT, COMM,
IERROR)
INTEGER OP は Fortran では整数である.
void *sendbuf は MPI_IN_PLACE を指定可能.
void *recvbuf は受信するデータの最初のアドレスを指定する.int root で指
定されたプロセス以外では何を指定しても良い.
MPI_Op op は演算の種類を指定する(後述)
int root は,データを受信するプロセス番号を指定する.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
53
/ 72
MPI REDUCE
プロセス 0 (受信) z0 z1 z2
プロセス 0 (送信) 1 5 a2
プロセス 1
2 6 b2
プロセス 2
3 7 c2
プロセス 3
4 8 d2
z3
a3
b3
c3
d3
...
...
...
...
...
z3
a3
b3
c3
d3
...
...
...
...
...
↓
プロセス 0 (受信) 10 26 z2
プロセス 0 (送信) 1 5 a2
プロセス 1
2 6 b2
プロセス 2
3 7 c2
プロセス 3
4 8 d2
計算科学入門 第 7 回
(2013 年 05 月 29 日)
54
/ 72
MPI オペランド
MPI_REDUCE,MPI_ALLREDUCE で用いることのできる演算 MPI_Op op.
MPI_MAX : 最大値
MPI_MIN : 最小値
MPI_SUM : 和
MPI_PROD : 積
MPI_LAND : 論理積
MPI_BAND : ビットごとに論理積を取る演算
MPI_LOR : 論理和
MPI_BOR : ビットごとに論理和を取る演算
MPI_LXOR : 排他的論理和
MPI_BXOR : ビットごとに排他的論理和を取る演算
MPI_MAXLOC : 最大値とその位置
MPI_MINLOC : 最小値とその位置
計算科学入門 第 7 回
(2013 年 05 月 29 日)
55
/ 72
例 ex11.c (4 プロセスで実行)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv){
int my_rank, num_proc;
int i, my[2], sum[2];
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &num_proc);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
for(i=0;i<2;i++) my[i] = i*4 + my_rank + 1;
MPI_Reduce(my, sum, 2, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
if(my_rank == 0) printf("%d %d\n", sum[0], sum[1]);
MPI_Finalize();
return 0;
}
実行結果
10 26
計算科学入門 第 7 回
(2013 年 05 月 29 日)
56
/ 72
MPI ALLTOALL
MPI_ALLTOALL は各プロセス持つデータを,一定数ごとに各プロセスに
分配していき,分配されたデータを結合する関数である.次のページの例を
参照.
int MPI_Alltoall(void *sendbuf, int sendcount,
MPI_Datatype sendtype, void *recvbuf, int recvcount,
MPI_Datatype recvtype, MPI_Comm comm)
MPI_ALLTOALL(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF, RECVCOUNT,
RECVTYPE, COMM, IERROR)
void *sendbuf は送信するデータの最初のアドレスを指定する.
int sendcount は,各プロセスが,1 プロセスあたりに送信するデータの数を指定
する.通常,int recvcount と同じになる.
void *recvbuf は受信するデータの最初のアドレスを指定する.
int recvcount は,各プロセスが,1 プロセスあたりから受信するデータの数を指
定する.通常,int sendcount と同じになる.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
57
/ 72
MPI ALLTOALL
プロセス 0 (送信)
プロセス 1 (送信)
プロセス 2 (送信)
プロセス 3 (送信)
a0
b0
c0
d0
a1
b1
c1
d1
a2
b2
c2
d2
a3
b3
c3
d3
...
...
...
...
b0
b1
b2
b3
c0
c1
c2
c3
d0
d1
d2
d3
...
...
...
...
↓
プロセス 0 (受信)
プロセス 1 (受信)
プロセス 2 (受信)
プロセス 3 (受信)
a0
a1
a2
a3
計算科学入門 第 7 回
(2013 年 05 月 29 日)
58
/ 72
MPI ALLGATHER
MPI_ALLGATHER は MPI_GATHER の結果を全てのプロセスに渡す関数で
ある.引数は MPI_GATHER との違いは root が無くなっただけである.
int MPI_Allgather(void *sendbuf, int sendcount,
MPI_Datatype sendtype, void *recvbuf, int recvcount,
MPI_Datatype recvtype, MPI_Comm Comm)
MPI_ALLGATHER(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF, RECVCOUNT,
RECVTYPE, COMM, IERROR)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
59
/ 72
MPI ALLGATHER
プロセス 0 (送信)
プロセス 1 (送信)
プロセス 2 (送信)
プロセス 3 (送信)
a0
b0
c0
d0
a1
b1
c1
d1
a2
b2
c2
d2
a3
b3
c3
d3
...
...
...
...
b0
b0
b0
b0
c0
c0
c0
c0
d0
d0
d0
d0
...
...
...
...
↓
プロセス 0 (受信)
プロセス 1 (受信)
プロセス 2 (受信)
プロセス 3 (受信)
a0
a0
a0
a0
計算科学入門 第 7 回
(2013 年 05 月 29 日)
60
/ 72
MPI ALLREDUCE
MPI_ALLREDUCE は MPI_REDUCE の結果を全てのプロセスに渡す関数で
ある.引数は MPI_REDUCE との違いは root が無くなっただけである.
int MPI_Reduce(void *sendbuf, void *recvbuf, int count,
MPI_Datatype type, MPI_Op op, MPI_Comm comm)
MPI_REDUCE(SENDBUF, RECVBUF, COUNT, TYPE, OP, COMM, IERROR)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
61
/ 72
MPI ALLREDUCE
プロセス 0 (送信)
プロセス 1 (送信)
プロセス 2 (送信)
プロセス 3 (送信)
1
2
3
4
5
6
7
8
a2
b2
c2
d2
a3
b3
c3
d3
...
...
...
...
↓
プロセス 0 (受信)
プロセス 1 (受信)
プロセス 2 (受信)
プロセス 3 (受信)
10
10
10
10
26
26
26
26
x2
y2
z2
u2
x3
y3
z3
u3
...
...
...
...
計算科学入門 第 7 回
(2013 年 05 月 29 日)
62
/ 72
補足
以降は今まで紹介しなかった関数などの紹介.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
63
/ 72
(補足)MPI GET COUNT の例 ex12.c (2 プロセス)
13
14
15
16
17
18
19
20
21
22
if(my_rank == 0){
char send[] = "How are you?";
MPI_Send(send, strlen(send)+1, MPI_CHAR, 1, 0, MPI_COMM_WORLD);
} else if(my_rank == 1){
char recv[100]; int cnt;
MPI_Recv(recv, 20, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &status);
printf("I recieved: %s\n", recv);
MPI_Get_count(&status, MPI_CHAR, &cnt);
printf("length = %d\n",cnt);
}
実行結果
I recieved: How are you?
length = 13
計算科学入門 第 7 回
(2013 年 05 月 29 日)
64
/ 72
(補足)MPI GET COUNT の例 ex13.c (2 プロセス)
13
14
15
16
17
18
19
20
21
22
if(my_rank == 0){
char send[] = "I send looooooooooooooooooong message. How are you?";
MPI_Send(send, strlen(send)+1, MPI_CHAR, 1, 0, MPI_COMM_WORLD);
} else if(my_rank == 1){
char recv[100]; int cnt;
MPI_Recv(recv, 20, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &status);
printf("I recieved: %s\n", recv);
MPI_Get_count(&status, MPI_CHAR, &cnt);
printf("length = %d\n",cnt);
}
実行結果
Abort
計算科学入門 第 7 回
(2013 年 05 月 29 日)
65
/ 72
MPI BARRIER
MPI_BARRIER は全プロセスが呼び出すまで待ち続ける関数である.
int MPI_Barrier(MPI_Comm comm)
MPI_BARRIER(COMM)
計算科学入門 第 7 回
(2013 年 05 月 29 日)
66
/ 72
(補足)MPI WAITALL
MPI_WAITALL は指定した複数の非ブロック通信が全て終了するまで待つ
関数である.
int MPI_Waitall(int count, MPI_Request *array_of_requests,
MPI_Status *array_of_statuses)
MPI_WAITALL(COUNT, ARRAY_OF_REQUESTS, ARRAY_OF_STATUSES, IERROR)
int count は待ちたい非ブロック通信の数を指定する.
MPI_Request *array_of_requests は待ちたい送受信の際に得られた値の配列
(要素数は int count)を指定する.
MPI_Status *array_of_statuses はステータスが代入される.必要ないなら,
MPI_STATUSES_IGNORE を指定すれば良い.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
67
/ 72
(補足)MPI TEST
MPI_TEST は指定した非ブロック通信が終了しているかどうかチェックす
る関数である.非ブロック版の MPI_WAIT と思うことができる.
int MPI_Test(MPI_Request *request, int *flag,
MPI_Status *status)
MPI_MPI_TEST(REQUEST, FLAG, STATUS, IERROR)
LOGICAL FLAG は Fortran では LOGICAL 型.
int flag は指定した通信が終わっていれば true な値,終わっていなければ false
な値(0)が代入される.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
68
/ 72
MPI WTIME
MPI_WTIME は,とある時点からの経過秒数を返す関数である.プログラム
の実行中は,とある時点は変更しないことが保証されており,2 回呼び出し,
差を取れば実行にかかった時間を計測することができる.
double MPI_Wtime(void)
DOUBLE PRECISION MPI_WTIME()
計算科学入門 第 7 回
(2013 年 05 月 29 日)
69
/ 72
例 ex14.c (4 プロセスで実行)
07
08
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
double start_time, end_time;
static double arr[10000000]; /* around 80MB */
start_time = MPI_Wtime();
if(my_rank == 0){
MPI_Send(arr, 10000000, MPI_DOUBLE, 1, 0, MPI_COMM_WORLD);
} else if(my_rank == 1){
MPI_Recv(arr, 10000000, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
} else if(my_rank == 2){
int i;
for(i=0;i<10000000;i++) arr[i] = 10000.0 / i;
}
end_time = MPI_Wtime();
printf("rank = %d, elapsed = %f = %f - %f\n",
my_rank, end_time-start_time, end_time, start_time);
計算科学入門 第 7 回
(2013 年 05 月 29 日)
70
/ 72
例 ex14.c (4 プロセスで実行)
実行結果
rank =
rank =
rank =
rank =
3,
0,
1,
2,
elapsed
elapsed
elapsed
elapsed
=
=
=
=
0.000000
0.044439
0.044445
0.062007
=
=
=
=
1369794662.705628
1369794662.750048
1369794662.750048
1369794662.767653
-
1369794662.705628
1369794662.705609
1369794662.705603
1369794662.705646
計算科学入門 第 7 回
(2013 年 05 月 29 日)
71
/ 72
(補足)MPI BSEND, MPI SSEND, MPI RSEND
MPI_SEND は,送信サイズなどに応じて,以下の 3 つの関数を使い分けて
いる(使い分け方は処理系依存).以下の関数は引数などは,MPI_SEND と
同じ.受信はすべて MPI_RECV で良い.
MPI_BSEND : 受信側の準備ができていれば送信し,そうでなければ,送信バッファに転
送データをコピーして戻る.B は Buffered の意味.
MPI_SSEND : データを送信したら戻る.S は Synchronous の意味.
MPI_RSEND : 受信側の準備ができている場合のみ使用できる.R は Ready の意味.
ex7.c の例では MPI_BSEND,ex8.c の例では MPI_SSEND が用いられたと思
われる.
これらの非ブロック通信版は MPI_IBSEND,MPI_ISSEND,MPI_IRSEND.
引数などは MPI_ISEND と同じ.
計算科学入門 第 7 回
(2013 年 05 月 29 日)
72
/ 72