MPIによるプログラミング概要（その１）【C言語編】

KOBE HPC Summer School 2016
中島研吾（東大・情報基盤センター）
横川三津夫（神戸大・計算科学教育センター）

MPI Programming

1

本schoolの目的• 並列計算機の使用によって，より大規模で詳細なシミュレー
ションを高速に実施することが可能になり，新しい科学の開拓が期待される・・・• 並列計算の目的
– 高速
– 大規模
– 「大規模」の方が「新しい科学」という観点からのウェイトとしては高い．しかし，「高速」ももちろん重要である．
– +複雑– 理想：Scalable• N倍の規模の計算をN倍のCPUを使って，「同じ時間で」解く

MPI Programming2

概要• MPIとは• MPIの基礎： Hello Worldを並列で出力する• 全体データと局所データ• グループ通信（Collective Communication）• 1対1通信（Peer-to-Peer Communication）

MPI Programming

3

概要• MPIとは• MPIの基礎： Hello Worldを並列で出力する• 全体データと局所データ• グループ通信（Collective Communication）• 1対1通信（Peer-to-Peer Communication）

MPI Programming

4

MPIとは （1/2）• Message Passing Interface• 分散メモリ間のメッセージ通信APIの「規格」– プログラムやライブラリそのものではない• 歴史– 1992– 1994– 1997– 2012

MPIフォーラム
MPI-1規格
MPI-2規格：MPI I/Oなど
MPI-3規格：• 実装（こっちはライブラリ）– MPICH： アルゴンヌ国立研究所– OpenMP, MVAPICHなど– 各ベンダーのMPIライブラリ• C/C++，Fortran，Java ; Unix，Linuｘ，Windows，Mac OS

MPI Programming

5

MPIとは （2/2）• 現状では，MPICH（フリー）が広く使用されている．
– 部分的に「MPI-2」規格をサポート– 2005年11月から「MPICH2」に移行• http://www.mpich.org/• MPIが普及した理由– MPIフォーラムによる規格統一• どんな計算機でも動く• Fortran，Cからサブルーチンとして呼び出すことが可能– MPICHの存在• フリー，あらゆるアーキテクチュアをサポート

MPI Programming6

参考文献• P.Pacheco 「MPI並列プログラミング」，培風館，2001（原著1997）• W.Gropp他「Using MPI second edition」，MIT Press, 1999.
• M.J.Quinn「Parallel Programming in C with MPI and OpenMP」,
McGrawhill, 2003.
• W.Gropp他「MPI：The Complete Reference Vol.I, II」，MIT Press,
1998.
• MPICH2– http://www.mpich.org/– API（Application Interface）の説明

MPI Programming

7

MPIを学ぶにあたって（1/2）• 文法– 「MPI-1」の基本的な機能（10程度）について習熟する．
• MPI-2では色々と便利な機能があるが・・・– あとは自分に必要な機能について調べる，あるいは知っている人，
知っていそうな人に尋ねる．

• 実習の重要性– プログラミング– その前にまず実行してみること• SPMD/SIMDのオペレーションに慣れること・・・「つかむ」こと– Single Program/Instruction Multiple Data
– 基本的に各プロセスは「同じことをやる」が「データが違う」• 大規模なデータを分割し，各部分について各プロセス（プロセッサ）が計算する– 全体データと局所データ，全体番号と局所番号

MPI Programming

8

SPMDのイメージ
それぞれの人が，本棚に一連のノートを持っている．
それぞれの人には，名前が付いている（人を区別できる）．
ノートには同じ名前が付けられているが，中身は違っている．

本棚のノートに対し，それぞれの人が，読んだり書いたり…
大体は同じ作業をするが，最初のノートの中身が違うので，中身はそれぞれ違う．
ある人に，他の人とは違う作業をさせた
い場合には，名前で作業と指示してあげる．

【必要な情報】
ノートの名前
何冊
誰に荷物のタグなど

時々，他の人のノートを見たい．
相手にノートの中身を送ってあげる．
送られた人は，それを違う名前のノートに中身を書き写す．

MPI Programming

9

PE: Processing Element
プロセッサ，領域，プロセス

SPMD

mpirun -np M <Program>

この絵が理解できればMPIは9割方，理解できたことになる．
コンピュータサイエンスの学科でもこれを上手に教えるのは難しいらしい．

PE #0

PE #1

PE #2

PE #M-1

Program

Program

Program

ProgramData #0

Data #1

Data #2

Data #M-1

各プロセスでは「同じプログラムが動く」が「データが違う」
大規模なデータを分割し，各部分について各プロセス（プロセッサ）が計算する通信以外は，単体CPUのときと同じ，というのが理想

MPI Programming用語

10• プロセッサ，コア– ハードウェアとしての各演算装置．シングルコアではプロセッサ＝コア• プロセス– MPI計算のための実行単位，ハードウェア的な「コア」とほぼ同義．
– しかし1つの「プロセッサ・コア」で複数の「プロセス」を起動する場合もある（効率的ではないが）．

• PE（Processing Element）– 本来，「プロセッサ」の意味なのであるが，本講義では「プロセス」の意
味で使う場合も多い．次項の「領域」とほぼ同義でも使用．
• マルチコアの場合は：「コア＝PE」という意味で使うことが多い．

• 領域– 「プロセス」とほぼ同じ意味であるが，SPMDの「MD」のそれぞれ一つ，「各データ」の意味合いが強い．しばしば「PE」と同義で使用．

• MPIのプロセス番号（PE番号，領域番号）は0から開始– したがって8プロセス（PE，領域）ある場合は番号は0～7

MPI Programming

11

PE: Processing Element
プロセッサ，領域，プロセス

SPMD

mpirun -np M <Program>

この絵が理解できればMPIは9割方，理解できたことになる．
コンピュータサイエンスの学科でもこれを上手に教えるのは難しいらしい．

PE #0

PE #1

PE #2

PE #M-1

Program

Program

Program

ProgramData #0

Data #1

Data #2

Data #M-1

各プロセスでは「同じプログラムが動く」が「データが違う」
大規模なデータを分割し，各部分について各プロセス（プロセッサ）が計算する通信以外は，単体CPUのときと同じ，というのが理想

MPI Programming

12

講義，課題の予定• MPIサブルーチン機能– 環境管理– グループ通信– 1対1通信• 8月18日（火）– 環境管理，グループ通信（Collective Communication）• 課題S1• 8月19日（水）– 1対1通信（Point-to-Point Communication）• 課題S2： 一次元熱伝導解析コードの「並列化」– ここまでできればあとはある程度自分で解決できます．

MPI Programming

13

概要• MPIとは• MPIの基礎：Hello Worldを並列で出力する• 全体データと局所データ• グループ通信（Collective Communication）• 1対1通信（Peer-to-Peer Communication）

14

schoolで利用するコンピュータ
LAN-computer上のジョブ
実行はバッチジョブ

ログインサーバ
Fujitsu Primergy RX300 S6• CPU：Intel Xeon E5645@2.4GHz,
6コア x 2 sockets• メモリ 94GB

各自のPC

-computer

Fujitsu PRIMEHPC FX10
96ノード，ノードあたり• CPU：SPARC64 IXfx@1.65GHz,
16コア，211.2GFLOPS• メモリ： 32GB/ノード

神戸大学統合研究拠点（ポートアイランド）

MPI Programming

ログイン，ディレクトリ作成 on コンピュータ
ssh xxxxxxx@pi.ircpi.kobe-u.ac.jp
ディレクトリ作成>$ cd>$ mkdir 2016summer （好きな名前でよい）>$ cd 2016summer
このディレクトリを本講義では <$P-TOP> と呼ぶ
基本的にファイル類はこのディレクトリにコピー，解凍する

15

MPI Programming

16

ファイルコピー
Fortranユーザ>$ cd <$P‐TOP>>$ cp /tmp/2016summer/F/s1‐f.tar .
>$ tar xvf s1‐f.tar
Cユーザ>$ cd <$P‐TOP>>$ cp /tmp/2016summer/C/s1‐c.tar .
>$ tar xvf s1‐c.tar
ディレクトリ確認>$ ls
mpi>$ cd mpi/S1
このディレクトリを本講義では <$P‐S1> と呼ぶ．
<$P‐S1> = <$P‐TOP>/mpi/S1

MPI Programming

17

まずはプログラムの例
hello.f

implicit REAL*8 (A‐H,O‐Z)
include 'mpif.h'
integer :: PETOT, my_rank, ierrcall MPI_INIT(ierr)
call MPI_COMM_SIZE (MPI_COMM_WORLD, PETOT, ierr )call MPI_COMM_RANK (MPI_COMM_WORLD, my_rank, ierr )write (*,'(a,2i8)') 'Hello World Fortran', my_rank, PETOT
call MPI_FINALIZE (ierr)
stop
end

hello.c#include "mpi.h"#include <stdio.h>
int main(int argc, char **argv){
int n, myid, numprocs, i;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
printf ("Hello World %d¥n", myid);
MPI_Finalize();}

MPI Programming

hello.f/c をコンパイルしてみよう！
>$ mpifrtpx –Kfast hello.f>$ mpifccpx –Kfast hello.c

Fortran$> mpifrtpx –Kfast hello.f“mpifrtpx”：
Fortran90＋MPIによってプログラムをコンパイルする際に必要なコンパイラ，ライブラリ等がバインドされているコマンド

C言語$> mpifccpx –Kfast hello.c“mpifccpx”：
C＋MPIによってプログラムをコンパイルする際に必要な，コンパイラ，ライブラリ等がバインドされているコマンド
18

MPI Programming

ジョブ実行• 実行方法– 基本的にバッチジョブのみ– 会話型の実行は「基本的に」やりません．

• 実行手順– ジョブスクリプトを書きます．
– ジョブを投入します．
– ジョブの状態を確認します．
– 結果を確認します．

• その他– 実行時には1ノード（16コア）が占有されます．
– 他のユーザーのジョブに使われることはありません．
19

MPI Programming

ジョブスクリプト• <$P-S1>/hello.sh• スケジューラへの指令 ＋ シェルスクリプト#!/bin/sh#PJM ‐L “node=1“#PJM ‐L “elapse=00:00:30“#PJM ‐L “rscgrp=small“#PJM ‐j#PJM ‐o “hello.lst“#PJM ‐‐mpi “proc=4“

標準出力ファイル名
MPIプロセス数

mpiexec ./a.out

実行ファイル名

8プロセス“node=1““proc=8”

16プロセス“node=1““proc=16”

32プロセス“node=2““proc=32”

ノード数
実行時間
実行キュー名64プロセス“node=4““proc=64”

192プロセス“node=12““proc=192”

20

MPI Programming

ジョブ投入>$ pjsub hello.sh>$ cat hello.lst
Hello World Fortran
Hello World Fortran
Hello World Fortran
Hello World Fortran

0
2
3
1

4
4
4
4

21

MPI Programming

ジョブ投入，確認等• ジョブの投入• ジョブの確認• ジョブの取り消し・強制終了• キューの状態の確認• 同時実行・投入可能数

pjsub スクリプト名
pjstatpjdel ジョブID
pjstat --rsc
pjstat --limit[pi:~/2016summer/mpi/S1]$ pjstat
ACCEPT QUEUED
0
0
s
0
0
JOB_ID
73804

STGIN
0
0

JOB_NAME
hello.sh

READY RUNING RUNOUT STGOUT
0
1
0
0
0
1
0
0

MD ST USER
START_DATENM RUN yokokawa 07/15 17:12:26

HOLD
0
0

ERROR
0
0

TOTAL
1
1

ELAPSE_LIM NODE_REQUIRE
0000:00:10 1
22

MPI Programming

23

環境管理ルーチン＋必須項目
implicit REAL*8 (A‐H,O‐Z)
include 'mpif.h‘
integer :: PETOT, my_rank, ierrcall MPI_INIT(ierr)
call MPI_COMM_SIZE (MPI_COMM_WORLD, PETOT, ierr )call MPI_COMM_RANK (MPI_COMM_WORLD, my_rank, ierr )write (*,'(a,2i8)') 'Hello World Fortran', my_rank, PETOT
call MPI_FINALIZE (ierr)
stop
end

環境変数デフォルト値
Fortran90ではuse mpi可MPI_Init
初期化

MPI_Comm_size
プロセス数取得
mpirun -np XX <prog>#include "mpi.h"#include <stdio.h>
int main(int argc, char **argv){
int n, myid, numprocs, i;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
printf ("Hello World %d¥n", myid);
MPI_Finalize();}‘mpif.h’, “mpi.h”

MPI_Comm_rank
プロセスID取得
自分のプロセス番号（0から開始）

MPI_Finalize
MPIプロセス終了

MPI Programming

24

Fortran/Cの違い• 基本的にインタフェースはほとんど同じ– Cの場合，「MPI_Comm_size」のように「MPI」は大文字，「MPI_」のあとの最初の文字は大文字，以下小文字• Fortranはエラーコード（ierr）の戻り値を引数の最後に指定する必要がある．
• Cは変数の特殊な型がある.
– MPI_Comm, MPI_Datatype, MPI_Op etc.

• 最初に呼ぶ「MPI_Init」だけは違う– call MPI_INIT (ierr)– MPI_Init (int *argc, char ***argv)

MPI Programming25

何をやっているのか ?
implicit REAL*8 (A‐H,O‐Z)
include 'mpif.h‘
integer :: PETOT, my_rank, ierrcall MPI_INIT(ierr)
call MPI_COMM_SIZE (MPI_COMM_WORLD, PETOT, ierr )call MPI_COMM_RANK (MPI_COMM_WORLD, my_rank, ierr )write (*,'(a,2i5)') 'Hello World Fortran', my_rank, PETOT
call MPI_FINALIZE (ierr)
stop
end

• mpiexec により4つのプロセスが立ち上がる（今の場合は”proc=4”）．
– 同じプログラムが4つ流れる．
– データの値（my_rank）を書き出す．

• 4つのプロセスは同じことをやっているが，データとして取得したプロセスID（my_rank）は異なる．
• 結果として各プロセスは異なった出力をやっていることになる．
• まさにSPMDMPI Programming

26

mpi.h，mpif.h
implicit REAL*8 (A-H,O-Z)
include 'mpif.h‘
integer :: PETOT, my_rank, ierrcall MPI_INIT(ierr)
call MPI_COMM_SIZE (MPI_COMM_WORLD, PETOT, ierr )call MPI_COMM_RANK (MPI_COMM_WORLD, my_rank, ierr )write (*,'(a,2i8)') 'Hello World Fortran', my_rank, PETOT
call MPI_FINALIZE (ierr)stop
end#include "mpi.h"#include <stdio.h>
int main(int argc, char **argv){
int n, myid, numprocs, i;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
printf ("Hello World %d¥n", myid);
MPI_Finalize();}• MPIに関連した様々なパラメータおよび初期値を記述．
• 変数名は「MPI_」で始まっている．
• ここで定められている変数は，MPIサ
ブルーチンの引数として使用する以外は陽に値を変更してはいけない．
• ユーザーは「MPI_」で始まる変数を独自に設定しないのが無難．

MPI Programming

27

MPI_Init• MPIを起動する．他のMPI関数より前にコールする必要がある（必須）• 全実行文の前に置くことを勧める• MPI_Init (argc, argv)#include "mpi.h"#include <stdio.h>
int main(int argc, char **argv){
int n, myid, numprocs, i;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
printf ("Hello World %d¥n", myid);
MPI_Finalize();}CMPI Programming

28

MPI_Finalize

C• MPIを終了する．他の全てのMPI関数より後にコールする必要がある（必須）．
• 全実行文の後に置くことを勧める• これを忘れると大変なことになる．
– 終わったはずなのに終わっていない・・・• MPI_Finalize ()#include "mpi.h"#include <stdio.h>
int main(int argc, char **argv){
int n, myid, numprocs, i;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
printf ("Hello World %d¥n", myid);
MPI_Finalize();}

MPI Programming

29

MPI_Comm_size•Cコミュニケーター 「comm」で指定されたグループに含まれるプロセス数の合計が「size」にもどる．必須では無いが，利用することが多い．

• MPI_Comm_size (comm, size)– comm– sizeMPI_Comm

整数

IO

コミュニケータを指定する
comm.で指定されたグループ内に含まれるプロセス数の合計#include "mpi.h"#include <stdio.h>
int main(int argc, char **argv){
int n, myid, numprocs, i;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
printf ("Hello World %d¥n", myid);
MPI_Finalize();}

MPI Programming

30

コミュニケータとは ?

C

MPI_Comm_Size (MPI_COMM_WORLD, PETOT)• 通信を実施するためのプロセスのグループを示す．
• MPIにおいて，通信を実施する単位として必ず指定する必要がある．
• mpiexecで起動した全プロセスは，デフォルトで「MPI_COMM_WORLD」というコミュニケータで表されるグ
ループに属する．
• 複数のコミュニケータを使用し，異なったプロセス数を割り当てることによって，複雑な処理を実施することも可能．
– 例えば計算用グループ，可視化用グループ• この授業では「MPI_COMM_WORLD」のみでOK．

MPI Programming

31

コミュニケータの概念
あるプロセスが複数のコミュニケータグループに属しても良い
MPI_COMM_WORLD
COMM_MANTLE

COMM_CRUSTCOMM_VIS

32
MPI Programming

MPI_Comm_rank•

コミュニケーター 「comm」で指定されたグループ内におけるプロセスIDが「rank」にもどる．必須では無いが，利用することが多い．
– プロセスIDのことを「rank（ランク）」と呼ぶことも多い．

• MPI_Comm_rank (comm, rank)– comm– rankMPI_Comm

整数

IO

コミュニケータを指定する
comm.で指定されたグループにおけるプロセスID
0から始まる（最大はPETOT-1）#include "mpi.h"#include <stdio.h>
int main(int argc, char **argv){
int n, myid, numprocs, i;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
printf ("Hello World %d¥n", myid);
MPI_Finalize();
}
32

C

33
MPI ProgrammingC

MPI_Abort• MPIプロセスを異常終了する．
• MPI_Abort (comm, errcode)– comm– errcode

33

MPI_Comm

整数

IO

コミュニケータを指定する
エラーコード

34
MPI Programming

C

MPI_Wtime•

時間計測用の関数：精度はいまいち良くない（短い時間の場合）• time= MPI_Wtime ()– timeR8…
double Stime, Etime;
Stime= MPI_Wtime ();(…)
Etime= MPI_Wtime ();34

O

過去のある時間からの経過時間（秒数）MPI Programming

35

MPI_Wtime の例$> mpifccpx –O1 time.c$> mpifrtpx –O1 time.f$> pjsub go4.sh$> cat test.lst
2
3.399327E‐06
13.499910E‐06
03.499910E‐06
33.399327E‐06
プロセス番号

計算時間

MPI Programming

36

MPI_Wtick• MPI_Wtimeでの時間計測精度を確認する．
• ハードウェア，コンパイラによって異なる• time= MPI_Wtick ()– timeR8O

implicit REAL*8 (A-H,O-Z)
include 'mpif.h'…
TM= MPI_WTICK ()write (*,*) TM…

時間計測精度（単位：秒）
double Time;…
Time = MPI_Wtick();
printf("%5d%16.6E¥n", MyRank, Time);…

MPI Programming

MPI_Wtick の例$> cd <$P‐S1>$> mpifccpx –O1 wtick.c$> mpifrtpx –O1 wtick.f$> pjsub go1.sh$> cat test.lst
01.000000E‐07$>

37

MPI Programming

38

C

MPI_Barrier•

コミュニケーター 「comm」で指定されたグループに含まれるプロセスの同期をとる．コミュニケータ「comm」内の全てのプロセスがこのサブルーチンを通らない限
り，次のステップには進まない．
• 主としてデバッグ用に使う．オーバーヘッドが大きいので，実用計算には使わない
方が無難．
• MPI_Barrier (comm)– commMPI_Comm

I

コミュニケータを指定する

MPI Programming

39

概要• MPIとは• MPIの基礎：Hello World• 全体データと局所データ• グループ通信（Collective Communication）• 1対1通信（Peer-to-Peer Communication）

MPI Programming

データ構造とアルゴリズム• コンピュータ上で計算を行うプログラムはデータ構造とアルゴリズムから構成される．
• 両者は非常に密接な関係にあり，あるアルゴリズムを実現するためには，それに適したデータ構造が必要である．
– 極論を言えば「データ構造＝アルゴリズム」と言っても良い．

• 並列計算を始めるにあたって，基本的なアルゴリズムに適したデータ構造を定める必要がある．

40

MPI Programming

SPMD：Single Program Multiple Data
• 一言で「並列計算」と言っても色々なものがあり，基本的なアルゴリズムも様々．
• 共通して言えることは，SPMD（Single Program Multiple
Data）• なるべく単体CPUのときと同じようにできることが理想– 通信が必要な部分とそうでない部分を明確にする必要があり．

41

MPI Programming

42

SPMDに適したデータ構造とは ?

PE #0

PE #1

PE #2

PE #3

Program

Program

Program

ProgramData #0

Data #1

Data #2

Data #3

MPI Programming

43

SPMDに適したデータ構造（1/2）• 大規模なデータ領域を分割して，各プロセッサ，プロセスで計算するのがSPMDの基本的な考え方• 例えば，長さNG(=20)のベクトルVGに対して，各要素を2倍する計算を考えてみよう．
integer, parameter :: NG= 20
real(kind=8), dimension(20) :: VG
do i= 1, NG
VG(i)= 2.0 * VG(i)enddo• これを4つのプロセッサで分担して計算する場合には，各プロセッサが20/4=5 ずつデータを持ち，それぞれが処理すればよい．

MPI Programming

44

SPMDに適したデータ構造（2/2）• すなわち，こんな感じ：
integer, parameter :: NL= 5
real(kind=8), dimension(5) :: VL
do i= 1, NL
VL(i)= 2.0 * VL(i)enddo• このようにすれば「一種類の」プログラム（Single Program）
で並列計算を実施できる．
– ただし，各プロセスにおいて，「VL」の中身が違う：Multiple Data
– 可能な限り計算を「VL」のみで実施することが，並列性能の高い計算へつながる．
– プログラムの形は，単体CPUの場合とほとんど変わらない．

MPI Programming

45

全体データと局所データ• VG– 領域全体– 1番から20番までの「全体番号」を持つ「全体データ（Global Data）」• VL– 各プロセス（PE，プロセッサ，領域）– 1番から5番までの「局所番号」を持つ「局所データ（Local Data）」– できるだけ局所データを有効に利用することで，高い並列性能が得られる．

MPI Programming

46

局所データの考え方：C「全体データ」VGの• 1～5番成分がPE#0• 6~10番成分がPE#1• 11～15番成分がPE#2• 16～20番成分がPE#3のそれぞれ，「局所データ」
VLの1番～5番成分となる（局所番号が1番～5番となる）．

Vg[ 0]
Vg[ 1]
Vg[ 2]
Vg[ 3]
Vg[ 4]
Vg[ 5]
Vg[ 6]
Vg[ 7]
Vg[ 8]
Vg[ 9]
Vg[10]
Vg[11]
Vg[12]
Vg[13]
Vg[14]
Vg[15]
Vg[16]
Vg[17]
Vg[18]
Vg[19]

C

PE#0

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

PE#1

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

PE#2

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

PE#3

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

46

MPI Programming

全体データと局所データ• VG– 領域全体– 1番から20番までの「全体番号」を持つ「全体データ（Global Data）」• VL– 各プロセッサ– 1番から5番までの「局所番号」を持つ「局所データ（Local Data）」• この講義で常に注意してほしいこと– VG（全体データ）からVL（局所データ）をどのように生成するか．
– VGからVL，VLからVGへデータの中身をどのようにマッピングするか．
– VLがプロセスごとに独立して計算できない場合はどうするか．
– できる限り「局所性」を高めた処理を実施する⇒高い並列性能• そのための「データ構造」，「アルゴリズム」を考える．
47

MPI Programming• MPIとは• MPIの基礎：Hello World• 全体データと局所データ• グループ通信（Collective Communication）• 1対1通信（Peer-to-Peer Communication）

48

MPI Programming

49

グループ通信とは• コミュニケータで指定されるグループ全体に関わる通信．
• 例– 制御データの送信– 最大値，最小値の判定– 総和の計算– ベクトルの内積の計算– 密行列の転置

MPI Programming

50

グループ通信の例（1/4）

P#0 A0 B0 C0 D0
P#1

P#0 A0 B0 C0 D0BroadcastP#1 A0 B0 C0 D0

P#2

P#2 A0 B0 C0 D0

P#3

P#3 A0 B0 C0 D0

P#0 A0 B0 C0 D0

P#0 A0

P#1
P#2
P#3

Scatter

GatherP#1 B0P#2 C0P#3 D0MPI Programming

51

グループ通信の例（2/4）

P#0 A0P#1 B0P#0 A0 B0 C0 D0
All gather

P#1 A0 B0 C0 D0P#2 C0P#2 A0 B0 C0 D0P#3 D0P#3 A0 B0 C0 D0

P#0 A0 A1 A2 A3

P#0 A0 B0 C0 D0P#1 B0 B1 B2 B3All-to-All

P#1 A1 B1 C1 D1P#2 C0 C1 C2 C3P#2 A2 B2 C2 D2P#3 D0 D1 D2 D3P#3 A3 B3 C3 D3

MPI Programming

52

グループ通信の例（3/4）

P#0 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

P#0 A0 B0 C0 D0P#1 A1 B1 C1 D1Reduce

P#1

P#2 A2 B2 C2 D2

P#2

P#3 A3 B3 C3 D3

P#3

P#0 A0 B0 C0 D0

P#0 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

P#1 A1 B1 C1 D1All reduceP#1 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

P#2 A2 B2 C2 D2P#2 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

P#3 A3 B3 C3 D3P#3 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

MPI Programming

53

グループ通信の例（4/4）

P#0 op.A0-A3

P#0 A0 B0 C0 D0P#1 A1 B1 C1 D1Reduce scatterP#1 op.B0-B3

P#2 A2 B2 C2 D2P#2 op.C0-C3

P#3 A3 B3 C3 D3P#3 op.D0-D3

MPI Programming

54

グループ通信による計算例• ベクトルの内積• Scatter/Gather• 分散ファイルの読み込み

MPI Programming

55

全体データと局所データ• 大規模な全体データ（global data）を局所データ（local
data）に分割して，SPMDによる並列計算を実施する場合のデータ構造について考える．

56

領域分割
• 1GB程度のPC → 106メッシュが限界：FEM– 1000km×1000km×100kmの領域（西南日本）を1kmメッシュで切ると108メッシュになる• 大規模データ → 領域分割，局所データ並列処理• 全体系計算 → 領域間の通信が必要

領域分割
大規模
データ

局所
局所
データ データ
局所
局所
データ データ

局所
データ

局所
データ
通信

局所
データ

局所
データ

PCのメモリに入りきらない
MPI Programming

57

局所データ構造• 対象とする計算（のアルゴリズム）に適した局所データ構造を定めることが重要– アルゴリズム＝データ構造• この講義の主たる目的の一つと言ってよい．

MPI Programming

MPI Programming

58

全体データと局所データ• 大規模な全体データ（global data）を局所データ（local
data）に分割して，SPMDによる並列計算を実施する場合のデータ構造について考える．
• 下記のような長さ20のベクトル，VECpとVECsの内積計算
を4つのプロセッサ，プロセスで並列に実施することを考える．
VECp( 1)=
( 2)=
( 3)=
…
(18)=
(19)=
(20)=

2
2
2
2
2
2

VECs( 1)=
( 2)=
( 3)=
…
(18)=
(19)=
(20)=

Fortran

3
3
3
3
3
3

VECp[ 0]=
[ 1]=
[ 2]=
…
[17]=
[18]=
[19]=

2
2
2

VECs[ 0]=
[ 1]=
[ 2]=
…
[17]=
[18]=
[19]=

2
2
2
C

3
3
3
3
3
3

MPI Programming

59<$P-S1>/dot.f, dot.c
implicit REAL*8 (A‐H,O‐Z)
real(kind=8),dimension(20):: &VECp, VECs
do i= 1, 20
VECp(i)= 2.0d0
VECs(i)= 3.0d0
enddo#include <stdio.h>
int main(){
int i;
double VECp[20], VECs[20]double sum;for(i=0;i<20;i++){VECp[i]= 2.0;
VECs[i]= 3.0;}

sum= 0.d0
do ii= 1, 20
sum= sum + VECp(ii)*VECs(ii)
enddo
stop
end

}

sum = 0.0;
for(i=0;i<20;i++){sum += VECp[i] * VECs[i];}return 0;

MPI Programming

60<$P-S1>/dot.f, dot.cの実行（実は不可）>$ cd <$T‐S1>>$ cc ‐O3 dot.c>$ f95 –O3 dot.f>$ ./a.out
1
2.00
2
2.00
3
2.00
…
18
19
20

2.00
2.00
2.00

dot product

3.00
3.00
3.00
3.00
3.00
3.00
120.00MPI Programming

61

MPI_Reduce•

P#0 A0 B0 C0 D0P#1 A1 B1 C1 D1P#0 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3
Reduce

P#1

P#2 A2 B2 C2 D2

P#2

P#3 A3 B3 C3 D3

P#3

コミュニケーター 「comm」内の，各プロセスの送信バッファ「sendbuf」について，
演算「op」を実施し，その結果を1つの受信プロセス「root」の受信バッファ「recbuf」に格納する．
– 総和，積，最大，最小 他• MPI_Reduce (sendbuf,recvbuf,count,datatype,op,root,comm)– sendbuf– recvbuf

任意
任意

I
O

– count
整数I– datatype MPI_Datatype I
FortranC– op

送信バッファの先頭アドレス，
受信バッファの先頭アドレス，
タイプは「datatype」により決定
メッセージのサイズ
メッセージのデータタイプ

MPI_INTEGER, MPI_REAL, MPI_DOUBLE_PRECISION, MPI_CHARACTER etc.
MPI_INT, MPI_FLOAT, MPI_DOUBLE, MPI_CHAR etc
MPI_Op

I

計算の種類

MPI_MAX, MPI_MIN, MPI_SUM, MPI_PROD, MPI_LAND, MPI_BAND etc

ユーザーによる定義も可能： MPI_OP_CREATE– root
整数I
受信元プロセスのID（ランク）– commMPI_Comm I
コミュニケータを指定する

C

MPI Programming

62

送信バッファと受信バッファ• MPIでは「送信バッファ」，「受信バッファ」という変数がしばしば登場する．
• 送信バッファと受信バッファは必ずしも異なった名称の配列である必要はないが，必ずアドレスが異なっていなければならない．

MPI Programming

MPI_Reduceの例（1/2） C
MPI_Reduce(sendbuf,recvbuf,count,datatype,op,root,comm)double X0, X1;
MPI_Reduce(&X0, &X1, 1, MPI_DOUBLE, MPI_MAX, 0, <comm>);double X0[4], XMAX[4];MPI_Reduce(X0, XMAX, 4, MPI_DOUBLE, MPI_MAX, 0, <comm>);

各プロセスにおける，X0[i]の最大値が0番プロセスのXMAX[i]に入る（i=0~3）

63

C

MPI Programming

MPI_Reduceの例（2/2） C
MPI_Reduce(sendbuf,recvbuf,count,datatype,op,root,comm)double X0, XSUM;
MPI_Reduce(&X0, &XSUM, 1, MPI_DOUBLE, MPI_SUM, 0, <comm>)

各プロセスにおける，X0の総和が0番PEのXSUMに入る．
double X0[4];MPI_Reduce(&X0[0], &X0[2], 2, MPI_DOUBLE_PRECISION, MPI_SUM, 0, <comm>)

各プロセスにおける，・ X0[0]の総和が0番プロセスのX0[2]に入る．
・ X0[1]の総和が0番プロセスのX0[3]に入る．

64

C

MPI Programming

65
P#0 A0 B0 C0 D0

MPI_Bcast•

P#1

P#0 A0 B0 C0 D0BroadcastP#1 A0 B0 C0 D0

P#2

P#2 A0 B0 C0 D0

P#3

P#3 A0 B0 C0 D0

コミュニケーター 「comm」内の一つの送信元プロセス「root」のバッファ「buffer」
から，その他全てのプロセスのバッファ「buffer」にメッセージを送信．

• MPI_Bcast (buffer,count,datatype,root,comm)– buffer

任意I/O– count

整数I– datatype MPI_Datatype I
FortranC– root– commバッファの先頭アドレス，
タイプは「datatype」により決定
メッセージのサイズ
メッセージのデータタイプ

MPI_INTEGER, MPI_REAL, MPI_DOUBLE_PRECISION, MPI_CHARACTER etc.
MPI_INT, MPI_FLOAT, MPI_DOUBLE, MPI_CHAR etc.

整数

I
MPI_Comm I

送信元プロセスのID（ランク）
コミュニケータを指定する

C

MPI Programming

66

MPI_Allreduce

P#0 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

P#0 A0 B0 C0 D0P#1 A1 B1 C1 D1All reduceP#1 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

P#2 A2 B2 C2 D2P#2 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3

P#3 A3 B3 C3 D3P#3 op.A0-A3 op.B0-B3 op.C0-C3 op.D0-D3• MPI_Reduce + MPI_Bcast• 総和，最大値を計算したら，各プロセスで利用したい場合が多い• call MPI_Allreduce(sendbuf,recvbuf,count,datatype,op, comm)– sendbuf– recvbuf
–
–
–
–

任意
任意

I
O

count
整数
I
datatype MPI_Datatype I
op
MPI_OpI
comm
MPI_Comm I

送信バッファの先頭アドレス，
受信バッファの先頭アドレス，
タイプは「datatype」により決定
メッセージのサイズ
メッセージのデータタイプ
計算の種類
コミュニケータを指定する

C

MPI Programming

67

MPI_Reduce/Allreduceの “op”
MPI_Reduce(sendbuf,recvbuf,count,datatype,op,root,comm)• MPI_MAX，MPI_MIN• MPI_SUM，MPI_PROD• MPI_LAND

最大値，最小値
総和，積
論理AND

C

MPI Programming

68

局所データの考え方（1/2）• 長さ20のベクトルを，4つに分割する• 各プロセスで長さ5のベクトル（1~5）
VECp[ 0]=
[ 1]=
[ 2]=
…
[17]=
[18]=
[19]=

2
2
2

VECs[ 0]=
[ 1]=
[ 2]=
…
[17]=
[18]=
[19]=

3
3
3

2
2
2

3
3
3

C

MPI Programming

69

C

局所データの考え方（2/2）• もとのベクトルの1～5番成分が0番PE，6~10番成分が1番PE，11～15
番が2番PE，16～20番が3番PEのそれぞれ1番～5番成分となる（局所
番号が1番～5番となる）．
VECp[ 0]~VECp[ 4]VECs[ 0]~VECs[ 4]VECp[ 5]~VECp[ 9]VECs[ 5]~VECs[ 9]VECp[10]~VECp[14]VECs[10]~VECs[14]PE#0

VECp[0]= 2[1]= 2[2]= 2[3]= 2[4]= 2

VECs[0]= 3[1]= 3[2]= 3[3]= 3[4]= 3

PE#1

VECp[0]= 2[1]= 2[2]= 2[3]= 2[4]= 2

VECs[0]= 3[1]= 3[2]= 3[3]= 3[4]= 3

PE#2

VECp[0]= 2[1]= 2[2]= 2[3]= 2[4]= 2

VECs[0]= 3[1]= 3[2]= 3[3]= 3[4]= 3

PE#3

VECp[0]= 2[1]= 2[2]= 2[3]= 2[4]= 2

VECs[0]= 3[1]= 3[2]= 3[3]= 3[4]= 3

VECp[15]~VECp[19]VECs[15]~VECs[19]MPI Programming

70

Cとは言え・・・• 全体を分割して，1（0）から番号をふり直すだけ・・・というのはいかにも簡単である．
• もちろんこれだけでは済ま
ない．済まない例については後半に紹介する．

Vg[ 0]
Vg[ 1]
Vg[ 2]
Vg[ 3]
Vg[ 4]
Vg[ 5]
Vg[ 6]
Vg[ 7]
Vg[ 8]
Vg[ 9]
Vg[10]
Vg[11]
Vg[12]
Vg[13]
Vg[14]
Vg[15]
Vg[16]
Vg[17]
Vg[18]
Vg[19]

PE#0

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

PE#1

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

PE#2

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

PE#3

Vl[0]
Vl[1]
Vl[2]
Vl[3]
Vl[4]

MPI Programming71

内積の並列計算例（１/3）<$P-S1>/allreduce.c#include <stdio.h>#include <stdlib.h>#include "mpi.h"
int main(int argc, char **argv){
int i,N;
int PeTot, MyRank;
double VECp[5], VECs[5];double sumA, sumR, sum0;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &PeTot);
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);
sumA= 0.0;
sumR= 0.0;
N=5;for(i=0;i<N;i++){VECp[i] = 2.0;
VECs[i] = 3.0;}
sum0 = 0.0;
for(i=0;i<N;i++){sum0 += VECp[i] * VECs[i];}各ベクトルを各プロセスで独立に生成する

C

MPI Programming

72

内積の並列計算例（2/3）<$P-S1>/allreduce.c
MPI_Reduce(&sum0, &sumR, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);MPI_Allreduce(&sum0, &sumA, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
printf("before BCAST %5d %15.0F %15.0F¥n", MyRank, sumA, sumR);
MPI_Bcast(&sumR, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
printf("after BCAST %5d %15.0F %15.0F¥n", MyRank, sumA, sumR);
MPI_Finalize();}

return 0;

C

MPI Programming73

内積の並列計算例（3/3）

C<$P-S1>/allreduce.c
MPI_Reduce(&sum0, &sumR, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);MPI_Allreduce(&sum0, &sumA, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);

内積の計算
各プロセスで計算した結果「sum0」の総和をとる
sumR には，PE#0の場合にのみ計算結果が入る．
sumA には，MPI_Allreduceによって全プロセスに計算結果が入る．
MPI_Bcast(&sumR, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);MPI_BCASTによって，PE#0以外の場合にも sumR に計算結果が入る．

MPI Programming

74<$P-S1>/allreduce.f/c の実行例$> mpifccpx –Kfast allreduce.c$> mpifrtpx –Kfast allreduce.f$> pjsub go4.sh ← 出力先のファイル名を適当に変更してもよい(my_rank, sumALLREDUCE, sumREDUCE)
before BCAST
01.200000E+02
after BCAST
01.200000E+02

1.200000E+02
1.200000E+02

before BCAST
after BCAST

1
11.200000E+02
1.200000E+02

0.000000E+00
1.200000E+02

before BCAST
after BCAST

33

1.200000E+02
1.200000E+02

0.000000E+00
1.200000E+02

before BCAST
after BCAST

221.200000E+02
1.200000E+02

0.000000E+00
1.200000E+02※ 出力される行の順番は変わる場合がある．

MPI Programming

75

グループ通信による計算例• ベクトルの内積• Scatter/Gather• 分散ファイルの読み込み

MPI Programming76

全体データと局所データ（1/3）• ある実数ベクトルVECgの各成分に実数を加えるという，以下のような簡単な計算を，「並列化」することを考えてみよう:

do i= 1, NG
VECg(i)= VECg(i) + ALPHA
enddo

for (i=0; i<NG; i++{VECg[i]= VECg[i] + ALPHA}MPI Programming

77

全体データと局所データ（2/3）• 簡単のために，– NG=32– ALPHA=1000.0– MPIプロセス数=4• ベクトルVECgとして以下のような32個の成分を持つベクトルを仮定する（<$P-S1>/a1x.all）：(101.0, 103.0, 105.0, 106.0, 109.0, 111.0, 121.0, 151.0,
201.0, 203.0, 205.0, 206.0, 209.0, 211.0, 221.0, 251.0,
301.0, 303.0, 305.0, 306.0, 309.0, 311.0, 321.0, 351.0,
401.0, 403.0, 405.0, 406.0, 409.0, 411.0, 421.0, 451.0)

MPI Programming

78

全体データと局所データ（3/3）•

並列計算の方針①

長さ32のベクトルVECgをあるプロセス（例えば0番）で読み込む．
– 全体データ②

4つのプロセスへ均等に（長さ8ずつ）割り振る．
– 局所データ，局所番号③④•

各プロセスでベクトル（長さ8）の各成分にALPHAを加える．
各プロセスの結果を再び長さ32のベクトルにまとめる．

もちろんこの程度の規模であれば1プロセッサで計算できるのであるが・・・

MPI Programming

79

Scatter/Gatherの計算 （1/8）
長さ32のベクトルVECgをあるプロセス（例えば0番）で読み込む．
• プロセス0番から「全体データ」を読み込む
include'mpif.h'integer, parameter :: NG= 32
real(kind=8), dimension(NG):: VECg
call MPI_INIT (ierr)call MPI_COMM_SIZE (<comm>, PETOT , ierr)call MPI_COMM_RANK (<comm>, my_rank, ierr)
if (my_rank.eq.0) thenopen (21, file= 'a1x.all', status= 'unknown')
do i= 1, NG
read (21,*) VECg(i)enddoclose (21)endif#include <mpi.h>#include <stdio.h>#include <math.h>#include <assert.h>
int main(int argc, char **argv){
int i, NG=32;
int PeTot, MyRank, MPI_Comm;
double VECg[32];char filename[80];FILE *fp;
MPI_Init(&argc, &argv);
MPI_Comm_size(<comm>, &PeTot);
MPI_Comm_rank(<comm>, &MyRank);
fp = fopen("a1x.all", "r");if(!MyRank) for(i=0;i<NG;i++){fscanf(fp, "%lf", &VECg[i]);}MPI Programming

80

Scatter/Gatherの計算 （2/8）
4つのプロセスへ均等に（長さ8ずつ）割り振る．
• MPI_Scatter の利用

MPI Programming

81

P#0 A0 B0 C0 D0MPI_Scatter

P#1
P#2
P#3

•

P#0 A0
Scatter
GatherP#1 B0P#2 C0P#3 D0

コミュニケーター 「comm」内の一つの送信元プロセス「root」の送信バッファ「sendbuf」から各プロセスに先頭から「scount」ずつのサイズのメッセージを送信
し，その他全てのプロセスの受信バッファ「recvbuf」に，サイズ「ｒcount」のメッセージを格納．

• MPI_Scatter (sendbuf, scount, sendtype, recvbuf, rcount,recvtype, root, comm)
–
–
–
–
–
–
–
–

sendbuf 任意
Iscount整数I
sendtype MPI_Datatype I
recvbuf 任意Orcount整数
I
recvtype MPI_Datatype I
root
整数
I
MPI_comm I
comm送信バッファの先頭アドレス，
送信メッセージのサイズ
送信メッセージのデータタイプ
受信バッファの先頭アドレス，
受信メッセージのサイズ
受信メッセージのデータタイプ
送信プロセスのID（ランク）
コミュニケータを指定する

C

MPI Programming

82

MPI_Scatter（続き）

P#0 A0 B0 C0 D0
P#1
P#2
P#3

P#0 A0
Scatter
GatherP#1 B0P#2 C0P#3 D0• MPI_Scatter (sendbuf, scount, sendtype, recvbuf, rcount,recvtype, root, comm)
–
–
–
–
–
–
–
–

•

sendbuf 任意
Iscount整数I
sendtype MPI_Datatype I
recvbuf 任意Orcount整数
I
recvtype MPI_Datatype I
root
整数
I
MPI_comm I
comm送信バッファの先頭アドレス，
送信メッセージのサイズ
送信メッセージのデータタイプ
受信バッファの先頭アドレス，
受信メッセージのサイズ
受信メッセージのデータタイプ
送信プロセスのID（ランク）
コミュニケータを指定する

通常は– scount = rcount– sendtype= recvtype•

この関数によって，プロセスroot番のsendbuf（送信バッファ）の先頭アドレスから
scount個ずつの成分が，commで表されるコミュニケータを持つ各プロセスに送信され，recvbuf（受信バッファ）のrcount個の成分として受信される．
C

MPI Programming

83

Scatter/Gatherの計算 （3/8）
4つのプロセスへ均等に（長さ8ずつ）割り振る．
• 各プロセスにおいて長さ8の受信バッファ「VEC」（＝局所データ）を定義
しておく．
• プロセス0番から送信される送信バッファ「VECg」の8個ずつの成分が，4つの各プロセスにおいて受信バッファ「VEC」の1番目から8番目の成分として受信される• N=8 として引数は下記のようになる：
integer, parameter :: N = 8
real(kind=8), dimension(N ) :: VEC...
call MPI_Scatter&(VECg, N, MPI_DOUBLE_PRECISION, &VEC , N, MPI_DOUBLE_PRECISION, &0, <comm>, ierr)

int N=8;
double VEC [8];...
MPI_Scatter (&VECg, N, MPI_DOUBLE, &VEC, N,
MPI_DOUBLE, 0, <comm>);

MPI_SCATTER(sendbuf, scount, sendtype, recvbuf, rcount,recvtype, root, comm )MPI Programming

84

Scatter/Gatherの計算 （4/8）
4つのプロセスへ均等に（長さ8ずつ）割り振る．
• rootプロセス（0番）から各プロセスへ8個ずつの成分がscatterされる．
• VECgの1番目から8番目の成分が0番プロセスにおけるVECの1番目から8番目，9番目から16番目の成分が1番プロセスにおけるVECの1番目
から8番目という具合に格納される．
– VECg：全体データ，VEC：局所データ
VEC
recvbuf

8

8

8

8

PE#0

PE#1

PE#2

PE#3

8

8

8
VECg
sendbuf8

局所データ
local data

全体データ
root global data

MPI Programming

85

Scatter/Gatherの計算 （5/8）
4つのプロセスへ均等に（長さ8ずつ）割り振る．
• 全体データ（global data）としてはVECgの1番から32番までの要素番号を持っていた各成分が，それぞれのプロセスにおける局所データ（local
data）としては，VECの1番から8番までの局所番号を持った成分として格納される．VECの成分を各プロセスごとに書き出してみると：
do i= 1, N
write (*,'(a, 2i8,f10.0)') 'before', my_rank, i, VEC(i)enddo

for(i=0;i<N;i++){printf("before %5d %5d %10.0F\n", MyRank, i+1, VEC[i]);}MPI Programming

86

Scatter/Gatherの計算 （5/8）
4つのプロセスへ均等に（長さ8ずつ）割り振る．
• 全体データ（global data）としてはVECgの1番から32番までの要素番号を持っていた各成分が，それぞれのプロセスにおける局所データ（local
data）としては，VECの1番から8番までの局所番号を持った成分として格納される．VECの成分を各プロセスごとに書き出してみると：

PE#0
before 0 1
before 0 2
before 0 3
before 0 4
before 0 5
before 0 6
before 0 7
before 0 8

101.
103.
105.
106.
109.
111.
121.
151.

PE#1
before 1 1
before 1 2
before 1 3
before 1 4
before 1 5
before 1 6
before 1 7
before 1 8

201.
203.
205.
206.
209.
211.
221.
251.

PE#2
before 2 1
before 2 2
before 2 3
before 2 4
before 2 5
before 2 6
before 2 7
before 2 8

301.
303.
305.
306.
309.
311.
321.
351.

PE#3
before 3 1
before 3 2
before 3 3
before 3 4
before 3 5
before 3 6
before 3 7
before 3 8

401.
403.
405.
406.
409.
411.
421.
451.

MPI Programming

87

Scatter/Gatherの計算 （6/8）
各プロセスでベクトル（長さ8）の各成分にALPHAを加える• 各プロセスでの計算は，以下のようになる：
real(kind=8), parameter :: ALPHA= 1000.
do i= 1, N
VEC(i)= VEC(i) + ALPHA
enddo

•

double ALPHA=1000.;...
for(i=0;i<N;i++){
VEC[i]= VEC[i] + ALPHA;}

計算結果は以下のようになる：
PE#0
after 0 1
after 0 2
after 0 3
after 0 4
after 0 5
after 0 6
after 0 7
after 0 8

1101.
1103.
1105.
1106.
1109.
1111.
1121.
1151.

PE#1
after 1 1
after 1 2
after 1 3
after 1 4
after 1 5
after 1 6
after 1 7
after 1 8

1201.
1203.
1205.
1206.
1209.
1211.
1221.
1251.

PE#2
after 2 1
after 2 2
after 2 3
after 2 4
after 2 5
after 2 6
after 2 7
after 2 8

1301.
1303.
1305.
1306.
1309.
1311.
1321.
1351.

PE#3
after 3 1
after 3 2
after 3 3
after 3 4
after 3 5
after 3 6
after 3 7
after 3 8

1401.
1403.
1405.
1406.
1409.
1411.
1421.
1451.

MPI Programming

88

Scatter/Gatherの計算 （7/8）
各プロセスの結果を再び長さ32のベクトルにまとめる• これには，MPI_Scatter と丁度逆の MPI_Gather という関数が用意されている．

MPI Programming

89

P#0 A0 B0 C0 D0

MPI_Gather

P#1
P#2
P#3

P#0 A0
Scatter
GatherP#1 B0P#2 C0P#3 D0• MPI_Scatterの逆• MPI_Gather (sendbuf, scount, sendtype, recvbuf, rcount,recvtype, root, comm )
–
–
–
–
–
–
–
–

•

sendbuf 任意
Iscount整数I
sendtype MPI_Datatype I
recvbuf 任意Orcount整数
I
recvtype MPI_Datatype I
root
整数
I
MPI_comm I
comm送信バッファの先頭アドレス，
送信メッセージのサイズ
送信メッセージのデータタイプ
受信バッファの先頭アドレス，
受信メッセージのサイズ
受信メッセージのデータタイプ
受信プロセスのID（ランク）
コミュニケータを指定する

ここで，受信バッファ recvbuf の値はroot番のプロセスに集められる．

C

MPI Programming

90

Scatter/Gatherの計算 （8/8）
各プロセスの結果を再び長さ32のベクトルにまとめる• 本例題の場合，root=0として，各プロセスから送信されるVECの成分を0番プロセスにおいてVECgとして受信するものとすると以下のようになる：
call MPI_Gather&(VEC , N, MPI_DOUBLE_PRECISION, &VECg, N, MPI_DOUBLE_PRECISION, &0, <comm>, ierr)•

MPI_Gather (&VEC, N, MPI_DOUBLE, &VECg, N,
MPI_DOUBLE, 0, <comm>);

各プロセスから8個ずつの成分がrootプロセスへgatherされる
VEC
sendbuf

8

8

8

8

PE#0

PE#1

PE#2

PE#3

8

8

8
VECg
recvbuf8

局所データ
local data

全体データ
root global data

MPI Programming

91<$P-S1>/scatter-gather.f/c
実行例$> mpifccpx –Kfast scatter‐gather.c$> mpifrtpx –Kfast scatter‐gather.f$> pjsub go4.sh← 出力先のファイル名を適当に変更してもよい
PE#0
before 0 1
before 0 2
before 0 3
before 0 4
before 0 5
before 0 6
before 0 7
before 0 8
PE#0
after 0 1
after 0 2
after 0 3
after 0 4
after 0 5
after 0 6
after 0 7
after 0 8

101.
103.
105.
106.
109.
111.
121.
151.

PE#1
before 1 1
before 1 2
before 1 3
before 1 4
before 1 5
before 1 6
before 1 7
before 1 8

1101.
1103.
1105.
1106.
1109.
1111.
1121.
1151.

PE#1
after 1 1
after 1 2
after 1 3
after 1 4
after 1 5
after 1 6
after 1 7
after 1 8

201.
203.
205.
206.
209.
211.
221.
251.

PE#2
before 2 1
before 2 2
before 2 3
before 2 4
before 2 5
before 2 6
before 2 7
before 2 8

1201.
1203.
1205.
1206.
1209.
1211.
1221.
1251.

PE#2
after 2 1
after 2 2
after 2 3
after 2 4
after 2 5
after 2 6
after 2 7
after 2 8

301.
303.
305.
306.
309.
311.
321.
351.

PE#3
before 3 1
before 3 2
before 3 3
before 3 4
before 3 5
before 3 6
before 3 7
before 3 8

401.
403.
405.
406.
409.
411.
421.
451.

1301.
1303.
1305.
1306.
1309.
1311.
1321.
1351.

PE#3
after 3 1
after 3 2
after 3 3
after 3 4
after 3 5
after 3 6
after 3 7
after 3 8

1401.
1403.
1405.
1406.
1409.
1411.
1421.
1451.

MPI Programming

92

MPI_Reduce_scatter
P#0 A0 B0 C0 D0P#1 A1 B1 C1 D1P#0 op.A0-A3
Reduce scatterP#1 op.B0-B3

P#2 A2 B2 C2 D2P#2 op.C0-C3

P#3 A3 B3 C3 D3P#3 op.D0-D3• MPI_Reduce + MPI_Scatter• MPI_Reduce_Scatter (sendbuf, recvbuf, rcount, datatype,op, comm)
–
–
–
–
–
–

sendbuf 任意
I
recvbuf 任意Orcount整数Idatatype MPI_Datatype I
op
MPI_OpI
comm
MPI_Comm I

送信バッファの先頭アドレス，
受信バッファの先頭アドレス，
受信メッセージのサイズ（配列：サイズ＝プロセス数）
メッセージのデータタイプ
計算の種類
コミュニケータを指定する

C

MPI Programming

93

P#0 A0

MPI_Allgather

P#1 B0P#0 A0 B0 C0 D0
All gather

P#1 A0 B0 C0 D0P#2 C0P#2 A0 B0 C0 D0P#3 D0P#3 A0 B0 C0 D0• MPI_Gather＋MPI_Bcast– Gatherしたものを，全てのPEにBcastする（各プロセスで同じデータを持つ）• MPI_Allgather (sendbuf, scount, sendtype, recvbuf, rcount,recvtype, comm)
–
–
–
–
–
–
–

sendbuf 任意
Iscount整数I
sendtype MPI_Datatype I
recvbuf 任意Orcount整数
I
recvtype MPI_Datatype I
comm
MPI_Comm I

送信バッファの先頭アドレス，
送信メッセージのサイズ
送信メッセージのデータタイプ
受信バッファの先頭アドレス，
受信メッセージのサイズ
受信メッセージのデータタイプ
コミュニケータを指定する

C

MPI Programming

94

P#0 A0 B0 C0 D0

P#0 A0 A1 A2 A3

MPI_Alltoall

P#1 B0 B1 B2 B3All-to-All

P#1 A1 B1 C1 D1P#2 C0 C1 C2 C3P#2 A2 B2 C2 D2P#3 D0 D1 D2 D3P#3 A3 B3 C3 D3• MPI_Allgatherの更なる拡張：転置• MPI_Alltoall (sendbuf, scount, sendtype, recvbuf, rcount,recvrype, comm)
–
–
–
–
–
–
–

sendbuf 任意
Iscount整数I
sendtype MPI_Datatype I
recvbuf 任意Orcount整数
I
recvtype MPI_Datatype I
comm
MPI_Comm I

送信バッファの先頭アドレス，
送信メッセージのサイズ
送信メッセージのデータタイプ
受信バッファの先頭アドレス，
受信メッセージのサイズ
受信メッセージのデータタイプ
コミュニケータを指定する

C

MPI Programming

95

グループ通信による計算例• ベクトルの内積• Scatter/Gather• 分散ファイルの読み込み

MPI Programming

96

分散ファイルを使用したオペレーション• Scatter/Gatherの例では，PE#0から全体データを読み込み，それを全体にScatterして並列計算を実施した．
• 問題規模が非常に大きい場合，1つのプロセッサで全てのデータを読み込むことは不可能な場合がある．
– 最初から分割しておいて，「局所データ」を各プロセッサで独立に読み込む．
– あるベクトルに対して，全体操作が必要になった場合は，状況に応じてMPI_Gatherなどを使用する

MPI Programming

97

分散ファイル読み込み：等データ長（1/2）>$ cd <$P‐S1>>$ ls a1.*
a1.0 a1.1 a1.2 a1.3>$ mpifccpx –Kfast file.c>$ mpifrtpx –Kfast file.f>$ pjsub go4.sh「a1x.all」を4つに分割したもの

MPI Programming98

分散ファイルの操作• 「a1.0~a1.3」は全体ベクトル「a1x.all」を領域に分割したもの，と考えることができる．
a1x.all

a1.0

a1.1

a1.2

a1.3

MPI Programming

99

分散ファイル読み込み：等データ長（2/2）<$P-S1>/file.c
int main(int argc, char **argv){
int i;
int PeTot, MyRank;
MPI_Comm SolverComm;
double vec[8];char FileName[80];FILE *fp;

Hello とそんなに変わらない

MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &PeTot);
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);
sprintf(FileName, "a1.%d", MyRank);
fp = fopen(FileName, "r");if(fp == NULL) MPI_Abort(MPI_COMM_WORLD, ‐1);
for(i=0;i<8;i++){fscanf(fp, "%lf", &vec[i]);}}「局所番号（0~7）」で読み込む

for(i=0;i<8;i++){printf("%5d%5d%10.0f¥n", MyRank, i+1, vec[i]);}MPI_Finalize();
return 0;

MPI Programming

100

SPMDの典型例

PE #0

PE #1

PE #2

PE #3

“a.out”“a.out”“a.out”“a.out”“a1.0”“a1.1”“a1.2”“a1.3”

mpiexec -np 4 a.out

MPI Programming

分散ファイル読み込み：可変長（1/2）
ファイル内のデータ数が均等でない場合はどうするか？
>$ cd <$P‐S1>>$ ls a2.*
a2.0 a2.1 a2.2 a2.3>$ cat a2.1
5
← 各PEにおける成分数201.0← 成分の並び
203.0
205.0
206.0
209.0
>$ mpifccpx –Kfast file2.c>$ mpifrtpx –Kfast file2.f>$ pjsub go4.sh101

MPI Programming

102

分散ファイルの読み込み：可変長（2/2）<$P-S1>/file2.c
int main(int argc, char **argv){
int i, int PeTot, MyRank;
MPI_Comm SolverComm;
double *vec, *vec2, *vecg;
int num;
double sum0, sum;char filename[80];FILE *fp;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &PeTot);
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);
sprintf(filename, "a2.%d", MyRank);
fp = fopen(filename, "r");assert(fp != NULL);

num が各データ（プロセッサ）で異なる

fscanf(fp, "%d", &num);
vec = malloc(num * sizeof(double));for(i=0;i<num;i++){fscanf(fp, "%lf", &vec[i]);}for(i=0;i<num;i++){printf(" %5d%5d%5d%10.0f¥n", MyRank, i+1, num, vec[i]);}MPI_Finalize();}

MPI Programming

103

局所データの作成法• 全体データ（N=NG）を入力– Scatterして各プロセスに分割– 各プロセスで演算– 必要に応じて局所データをGather（またはAllgather）して全体デー
タを生成• 局所データ（N=NL）を生成，あるいは（あらかじめ分割生成して）入力– 各プロセスで局所データを生成，あるいは入力– 各プロセスで演算– 必要に応じて局所データをGather（またはAllgather）して全体デー
タを生成• 将来的には後者が中心となるが，全体的なデータの動きを理解するために，しばらくは前者についても併用

MPI Programming

104

グループ通信による計算例• ベクトルの内積• Scatter/Gather• 分散ファイルの読み込み• MPI_Allgatherv

MPI Programming

105

MPI_Gatherv，MPI_Scatterv• これまで紹介してきた，MPI_GATHETR，
MPI_SCATTERなどは，各プロセッサからの送信，受信
メッセージが均等な場合．
• 末尾に「V」が付くと，各ベクトルが可変長さの場合となる．
– MPI_GATHERV– MPI_SCATTERV– MPI_ALLGATHERV– MPI_ALLTOALLVMPIprog.

106

MPI_Allgatherv• MPI_Allgather の可変長さベクトル版– 「局所データ」から「全体データ」を生成する• MPI_Allgatherv (sendbuf, scount, sendtype, recvbuf,
rcounts, displs, recvtype, comm)
–
–
–
–
–
–
–
–

sendbuf 任意
Iscount整数I
sendtype MPI_Datatype I
recvbuf 任意Orcounts 整数
I
displs整数
I
recvtype MPI_Datatype I
comm
MPI_Comm I

送信バッファの先頭アドレス，
送信メッセージのサイズ
送信メッセージのデータタイプ
受信バッファの先頭アドレス，
受信メッセージのサイズ（配列：サイズ＝PETOT）
受信メッセージのインデックス（配列：サイズ＝PETOT+1）
受信メッセージのデータタイプ
コミュニケータを指定する

C

MPIprog.

107

C

MPI_Allgatherv（続き）• MPI_Allgatherv (sendbuf, scount, sendtype, recvbuf,
rcounts, displs, recvtype, comm)
整数 I 受信メッセージのサイズ（配列：サイズ＝PETOT）
整数 I 受信メッセージのインデックス（配列：サイズ＝PETOT+1）– rcounts– displs– この2つの配列は，最終的に生成される「全体データ」のサイズに関する配列であるため，各プロセスで配列の全ての値が必要になる：• もちろん各プロセスで共通の値を持つ必要がある．

– 通常はstride(i)=rcounts(i)

PE#0

PE#1

stride[0]rcounts[0]displs[0]=0

stride[1]rcounts[1]PE#2

PE#(m-2) PE#(m-1)

stride[2]stride[m-2]

stride[m-1]rcounts[2]rcounts[m-2]rcounts[m-1]displs[1]=displs[0] + stride[0]size[recvbuf]= displs[PETOT]= sum[stride]

displs[m]=displs[m-1] + stride[m-1]MPIprog.

PE#0

PE#2

N
N

rcounts[2]

PE#1

N

stride[0]displs[1]rcounts[1]局所データから全体データを生成する

rcounts[0]MPI_Allgatherv
でやっていること

displs[0]stride[1]displs[2]stride[2]displs[3]Nrcounts[3]

PE#3

stride[3]displs[4]局所データ：sendbuf

全体データ：recvbuf

108

MPIprog.

109

MPI_Allgatherv
でやっていること

PE#1

N
N

PE#3

N

displs[2]stride[2]= rcounts[2]rcounts[3]

N

stride[1]= rcounts[1]displs[1]rcounts[2]PE#2

stride[0]= rcounts[0]rcounts[1]PE#0

rcounts[0]局所データから全体データを生成する

displs[0]stride[3]= rcounts[3]displs[3]displs[4]局所データ：sendbuf

全体データ：recvbuf

MPIprog.

110

MPI_Allgatherv詳細（1/2）

C• MPI_Allgatherv (sendbuf, scount, sendtype, recvbuf, rcounts,
displs, recvtype, comm)– rcounts– displs

整数
整数

I
I

受信メッセージのサイズ（配列：サイズ＝PETOT）
受信メッセージのインデックス（配列：サイズ＝PETOT+1）• rcounts– 各PEにおけるメッセージサイズ：局所データのサイズ• displs– 各局所データの全体データにおけるインデックス– displs(PETOT+1)が全体データのサイズ
PE#0

PE#1

stride[0]rcounts[0]displs[0]=0

stride[1]rcounts[1]PE#2

PE#(m-2) PE#(m-1)

stride[2]stride[m-2]

stride[m-1]rcounts[2]rcounts[m-2]rcounts[m-1]displs[1]=displs[0] + stride[0]size[recvbuf]= displs[PETOT]= sum[stride]

displs[m]=displs[m-1] + stride[m-1]MPIprog.

111

MPI_Allgatherv詳細（2/2）

C• rcountsとdisplsは各プロセスで共通の値が必要– 各プロセスのベクトルの大きさ N をallgatherして，rcountsに相当するベクトルを作る．
– rcountsから各プロセスにおいてdisplsを作る（同じものができる）．
• stride[i]= rcounts[i] とする– rcountsの和にしたがってrecvbufの記憶領域を確保する．
PE#0

PE#1

stride[0]rcounts[0]displs[0]=0

stride[1]rcounts[1]PE#2

PE#(m-2) PE#(m-1)

stride[2]stride[m-2]

stride[m-1]rcounts[2]rcounts[m-2]rcounts[m-1]displs[1]=displs[0] + stride[0]size[recvbuf]= displs[PETOT]= sum[stride]

displs[m]=displs[m-1] + stride[m-1]

MPI Programming

MPI_Allgatherv使用準備
例題：<$P-S1>/agv.f，<$P-S1>/agv.c• “a2.0”~”a2.3”から，全体ベクトルを生成する．
• 各ファイルのベクトルのサイズが，8,5,7,3であるから，長さ23（=8+5+7+3）のベクトルができることになる．

112

MPI Programming

113

a2.0~a2.3
PE#0

PE#1

PE#2

PE#3

8
101.0
103.0
105.0
106.0
109.0
111.0
121.0
151.0

5
201.0
203.0
205.0
206.0
209.0

7
301.0
303.0
305.0
306.0
311.0
321.0
351.0

3
401.0
403.0
405.0

MPIprog.

114

MPI_Allgatherv 使用準備（1/5）<$P-S1>/agv.c５
int main(int argc, char **argv){
int i;
int PeTot, MyRank;
MPI_Comm SolverComm;
double *vec, *vec2, *vecg;
int *Rcounts, *Displs;
int n;
double sum0, sum;char filename[80];FILE *fp;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &PeTot);
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);
sprintf(filename, "a2.%d", MyRank);
fp = fopen(filename, "r");assert(fp != NULL);
fscanf(fp, "%d", &n);
vec = malloc(n * sizeof(double));for(i=0;i<n;i++){fscanf(fp, "%lf", &vec[i]);}n（NL）の値が各PEで異なることに注意

CMPIprog.

115

MPI_Allgatherv 使用準備（2/5）

C<$P-S1>/agv.c
Rcounts= calloc(PeTot, sizeof(int));
Displs = calloc(PeTot+1, sizeof(int));
printf("before %d %d", MyRank, n);for(i=0;i<PeTot;i++){printf(" %d", Rcounts[i]);}MPI_Allgather(&n, 1, MPI_INT, Rcounts, 1, MPI_INT, MPI_COMM_WORLD);
printf("after %d %d", MyRank, n);for(i=0;i<PeTot;i++){printf(" %d", Rcounts[i]);}

各PEにRcountsを生成

Displs[0] = 0;

PE#0 N=8

Rcounts[0:3]= {8, 5, 7, 3}

PE#1 N=5

Rcounts[0:3]={8, 5, 7, 3}

PE#2 N=7
PE#3 N=3

MPI_Allgather

Rcounts[0:3]={8, 5, 7, 3}
Rcounts[0:3]={8, 5, 7, 3}

MPIprog.

116

MPI_Allgatherv 使用準備（3/5）

C<$P-S1>/agv.c
Rcounts= calloc(PeTot, sizeof(int));
Displs = calloc(PeTot+1, sizeof(int));
printf("before %d %d", MyRank, n);for(i=0;i<PeTot;i++){printf(" %d", Rcounts[i]);}MPI_Allgather(&n, 1, MPI_INT, Rcounts, 1, MPI_INT, MPI_COMM_WORLD);
printf("after %d %d", MyRank, n);for(i=0;i<PeTot;i++){printf(" %d", Rcounts[i]);}
Displs[0] = 0;
for(i=0;i<PeTot;i++){
Displs[i+1] = Displs[i] + Rcounts[i];}printf("CoundIndex %d ", MyRank);for(i=0;i<PeTot+1;i++){printf(" %d", Displs[i]);}MPI_Finalize();
return 0;}

各PEにRcountsを
生成
各PEでDisplsを
生成

MPI Programming

117

MPI_Allgatherv 使用準備（4/5）> cd <$P‐S1>> mpifccpx –Kfast agv.c> pjsub go4.sh
beforeafter
displs00088

beforeafter
displs1
1
1

55

beforeafter
displs33
3

33

beforeafter
displs

2
2
2

7
7

0
8
0

0
5
8

0
7
13

0
3
20

23

0
8
0

0
5
8

0
7
13

0
3
20

23

0
8
0

0
5
8

0
7
13

0
3
20

23

0
8
0

0
5
8

0
7
13

0
3
20

23

MPIprog.

118

MPI_Allgatherv 使用準備（5/5）• 引数で定義されていないのは「recvbuf」だけ．
• サイズは・・・「Displs[PETOT]」MPI_Allgatherv( VEC , N, MPI_DOUBLE,
recvbuf, rcounts, displs, MPI_DOUBLE,
MPI_COMM_WORLD);MPI Programming

119

課題S1 （1/2）• 「<$P-S1>/a1.0～a1.3」 ， 「<$P-S1>/a2.0~a2.3」から局
所ベクトル情報を読み込み，全体ベクトルのノルム（||x||）を求めるプログラムを作成する（S1-1）．
– ノルム ||x|| は，各要素の２乗の和の平方根である．
– <$P-S1>file.f，<$T-S1>file2.fをそれぞれ参考にする．

• 「<$P-S1>/a2.0~a2.3」から局所ベクトル情報を読み込み，「全体ベクトル」情報を各プロセッサに生成するプログラムを作成する．MPI_Allgathervを使用する（S1-2）．

MPI Programming120

課題S1 （2/2）• 下記の数値積分を台形公式によって求めるプログラムを作成する．MPI_Reduce，MPI_Bcast等を使用して並列化を実施
し，プロセッサ数を変化させた場合の計算時間を測定する（S1-3）．
4
0 1  x 2 dx
1

N


1
x  f1  f N 1 
2 f i 2 i 2