Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
並列プログラミング入門(MPI編)
高度情報科学技術研究機構 神戸センター 宮内 敦
0
本日の講義内容
並列プログラミングの予備知識
MPIの基礎知識
MPIプログラミングの初歩
MPI関数各論
並列性能評価
具体的な問題への応用 1
並列プログラミングの予備知識
1. ハードウェア観点からの分類
2. ソフトウェア観点からの分類
3. 通信方法からの分類
2
プロセッサ/メモリの配置による分類
共有記憶型 (Shared Memory)
分散記憶型 (Distributed Memory)
・全プロセッサが一つのメモリを共有 ・プロセッサ間の通信は不要 ・メモリアクセスの排他制御が必要 ・プロセッサ数~100
・プロセッサ毎に独立したメモリ ・プロセッサ間の通信が必要 ・局所メモリ内は排他制御不要 ・プロセッサ数~10,000
memory
interconnect
core
CPU
core
CPU
core
CPU
core
CPU
me
mo
ry
interconnect
core
CPU
core
CPU
core
CPU
core
CPU
me
mo
ry
me
mo
ry
me
mo
ry
3
マルチコアの場合
共有記憶型 (Shared Memory)
・CPU内のメモリコントローラを 相互通信網の一部と見做せば シングルコアの場合と同じ
分散共有記憶型 (Distributed Shared Memory)
≒ 8core共有 ≒ 2core共有×4socket分散
・分散と共有の両方の 特徴を併せ持つ
CPU
memory
interconnect
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
bus arbiter
me
mo
ry
interconnect
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
me
mo
ry
me
mo
ry
me
mo
ry
CPU
bus arbiter
4
GPUの場合
・分散共有記憶型の各ノード内にさらに共有記憶型を内包
異種分散共有記憶型 (Heterogeneously Distributed Shared Memory)
≒ (2core共有+12GPU-core共有)×4socket分散
GPU
bus arbiter
CPU
interconnect
local
memory
co
re
co
re
me
mo
ry co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
local
memory
co
re
co
re
me
mo
ry co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
local
memory
co
re
co
re
me
mo
ry co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
local
memory
co
re
co
re
me
mo
ry co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
co
re
5
アドレス空間による分類
単一アドレス空間 (Single Address Space)
・すべてのプロセッサが一つのアドレス空間を共有
・プロセッサ毎に独立したアドレス空間 ・プロセッサ/メモリは分散記憶型
分散アドレス空間 (Distributed Address Space)
UMA (Uniform Memory Access)
・プロセッサ/メモリは共有記憶型 ・空間内のどのアドレスにもアクセス時間は同じ
・プロセッサ/メモリは分散記憶型 ・一部のアドレスへのアクセスは速い ・その他のアドレスへのアクセスは遅い ・通信用キャッシュを持つ場合は ccNUMA (cache coherent NUMA)
NUMA (Non-Uniform Memory Access)
6
プロセッサの機能による分類 SMP (Symmetric Multi-Processing)
• 特権的なプロセッサがない、全てのプロセッサが同じ機能を持つ
AMP (Asymmetric Multi-Processing)
• メインプロセッサとサブプロセッサに分かれている
例: 共有記憶型 SGI-Origin 分散記憶型 京、地球シミュレータ、Beowulf etc.
例: 共有記憶型 分散記憶型 メインフレーム
7
• 1つのOSが管理する資源を複数のプロセッサが使用する • プロセッサ毎にOSが存在し、個別に資源を管理する
OSとプロセッサの関係による分類 密結合 (Tightly Coupled System)
疎結合 (Loosely Coupled System)
相互結合網 (Interconnect) による分類
8
相互結合網のバリエーションは多岐に亘る ここに挙げたのは代表的なものだけ*
*富田真治著「並列計算機構成論」昭晃堂(1986)に比較的まとまった記述がある
バス 同時にバスにアクセス できるのは1ノードだけ
n次元メッシュ 隣接ノード間のみの通信 平均ホップ数は√N程度
n次元トーラス 隣接ノード間のみの通信 平均ホップ数は√N程度
最大ホップ数はメッシュの半分
ファットツリー ツリー結合で上位が太い 平均ホップ数はlog2N程度
n次元ハイパーキューブ 各ノードがn個のノードと結合 平均ホップ数はlog2N程度
クロスバー スイッチで動的結合 1ホップでN対N通信
結線 ノード スイッチON スイッチOFF
Flynnの分類 (Flynn’s Taxonomy)
SIMD (Single Instruction, Multiple Data stream)
• 並列計算黎明期の分類法
• 計算機の動作を命令列とデータ列で抽象化・単純化
• 以下の4種類に分類
SISD (Single Instruction, Single Data stream)
MIMD (Multiple Instruction, Multiple Data stream)
MISD (Multiple Instruction, Single Data stream)
M. J. Flynn, Very High-Speed Computing Systems, Proceedings of IEEE , Vol. 54, 1901-1909, 1966
命令列とデータ列の単位をどう解釈するかで分類が変わる(例:VLIW) 最近ではプロセッサのSIMD命令以外は使われなくなった
9
並列プログラミングの予備知識
1. ハードウェア観点からの分類
2. ソフトウェア観点からの分類
3. 通信方法からの分類
10
プログラム実行方法からの分類 SPMD (Single Program Multiple Data stream)
• 全てのプロセス上で同一のプログラムを実行 • 使用するデータはプロセス毎に異なる • 一般にプロセス間通信の比率は高い (例外的にパラメータ並列の場合はプロセス間通信不要)
• プロセス毎に異なったプログラムを実行 • 使用するデータもプロセス毎に異なる • 一般にプロセス間通信の比率は低い (実際には純粋なMPMDは少なくSPMDとのハイブリッドが多い)
• 本来は共有記憶型のSIMDモデル (例: Thinking Machine CM2)
• 分散記憶型のSPMDもこれに分類される場合がある (例: 領域分割)
• プログラムを複数のタスクに分割し各プロセッサで並列処理 • MISD/MIMDモデルに近い概念 (例:master-worker/client-server )
MPMD (Multiple Program Multiple Data stream)
11
データ並列 (Data parallelism)
タスク並列 (Task parallelism)
並列プログラミングの予備知識
1. ハードウェア観点からの分類
2. ソフトウェア観点からの分類
3. 通信方法からの分類
12
メッセージ交換
PVM (Parallel Virtual Machine) ・最初期の規格、比較的単純な機能
・複合データ型や集団通信を提供する
MPI-1/2/3 (Message Passing Interface)
直接メモリアクセス
PGAS (Partitioned Global Address Space) ・分散記憶型に単一アドレスを提供する ・通常は言語に組込まれている (UPC, Chapel, Coarray etc.) ・ライブラリではOpenSHMEMがある(CrayT3Dのshmemのオープンソース版)
・片側通信として提供される ・単一アドレスは提供されない(ユーザプログラムで実現可能)
MPI-2/3 (Message Passing Interface)
送信側と受信側で通信呼び出し
送信側または受信側のみで通信呼び出し
13
結局・・・
・計算機の分類には様々な評価軸がある
・それぞれの軸は直交しておらず重複部分が少なくない
・最近の並列計算機は多層的・複合的な構成を持つ
・システム全体を一意に分類することは難しい
・様々な概念を理解することが重要
14
MPIの基礎知識
1. MPI規格 (MPI standard)
2. MPIライブラリ
15
•プロセス並列を前提としたプロセス間のメッセージ交換に関する標準仕様
•適用対象は分散記憶アーキテクチャー上の分散アドレス空間におけるSPMDモデル
•ネットワーク接続された異機種クラスター環境にも対応
•先行したPVM(Parallel Virtual Machine)に不足し
ていた機能を採り入れている
•現在ではスレッド並列・直接メモリアクセス・
MPMDの機能も提供されている
MPI とは
16
•MPI Forumが発行する規格書
•全ての基準となる最も重要な文献
•最新版はversion3.1、現在4.0へ向け改訂作業中 (4.0の関心はHybrid programming, Fault tolerance等)
http://mpi-forum.org/docs/mpi-3.1/mpi31-report.pdf
MPI Standard
*MPI Forum = 大学・研究機関・ベンダーから構成されるボランティアベースの協議会
Magna Carta 1215 (The British Library, UK)
=
17
バージョンの変遷と特徴
1.0 May ‘94
1.1 Jun ‘95
1.2 Jul ‘97
1.3 May ‘08
2.0 Jul ‘97
2.1 Jun ‘08
2.2 Sep ‘09
3.0 Sep ‘12
3.1 Jun ‘15
Point to Point Communication
Datatypes
Collective Communication
Process Topology
One-Sided Communications
Parallel I/O
Process Creation
Nonblocking Collectives
Enhancement in One-Sided Comm.
Enhancement in Process Topology
Tool Information Interface
Distributed Graph Topology
Final version
Minor Changes Error Corrections
18
規格の内容 (MPI standard 3.1)
・要素通信 (Point-to-Point Communication)
・データ型 (Datatypes)
・集団通信 (Collective Communication)
・コミュニケータ (Groups, Contexts, Communicators and Cashing )
・トポロジ (Process Topologies)
・環境管理 (MPI Environmental Management)
・情報オブジェクト (The Info Object)
・プロセス生成 (Process Creation and Management)
・片側通信 (One-Sided Communications)
・外部インターフェイス (External Interfaces)
・ファイル入出力 (I/O)
・ツール情報 (Tool Support)
19
注意すべき用語 ・バッファ(buffer)
MPIでは3種類のバッファが使われる
送受信バッファ(send/recv buffer)はデータを格納した変数・配列
システムバッファ(system buffer)はユーザに不可視(opaque)だが
デッドロックに関係する場合があり要注意
添付バッファ(attached buffer)はユーザ自身で割当てる
要素通信のバッファモードにおいてのみ使われる
・復帰(return)と完了(completion)
復帰は後続する命令を実行可能な状態 (注:returnを値を返すという意味で用いる場合もある)
完了は全ての手順が終了し通信前に戻った状態
・閉塞/非閉塞(blocking/nonblocking)
閉塞通信は送受信バッファが解放されるまで復帰しない
非閉塞通信は即座に復帰し、後で完了したか確認する
(これを遅延同期(defer synchronization)という)
・非同期通信(asynchronous communication)
非閉塞通信とは違う概念、MPIでは使わない* *The term asynchronous communication is not used in MPI, pp. 279, Using MPI, 2nd ed., MIT Press, 1999
20
参考図書等
・Using MPI Second Edition*, W. Gropp, E. Lusk and A. Skjellum, MIT press, 1999
・Using Advanced MPI W. Gropp, T. Hoefler, R. Thakur and E. Lusk, MIT press, 2014
・Parallel Programming in C with MPI and OpenMP M. J. Quinn, McGraw-Hill, 2008
・Parallel Programming with MPI P. Pacheco, Morgan Kaufmann, 1996
・Tutorials http://www.mcs.anl.gov/research/projects/mpi/tutorial/
* Using MPI Third Edition (https://mitpress.mit.edu/using-MPI-3ed) **query words = MPI, parallel computing, high performance computing, message passing, etc.
21
MPIの基礎知識
1. MPI規格(MPI Standard)
2. MPIライブラリ
22
特徴 ・MPI Standardに準拠して開発される ・プロセス並列モデル ・プロセス間通信でメッセージ交換 ・オープンソフトから商用ソフトまで多数 ・ライブラリにより性能の違いやバグ
代表的ライブラリ ・OpenMPI FT, LA, LAM joint team ☆現在の最大勢力 ・MPICH* Argonne National Lab. ☆根強い人気 ・MVAPICH Ohio State Univ. ☆GPUに強み ・LAM/MPI Indiana Univ. ★かつてクラスタ向けに人気 ・CHIMP Edinburgh Center ★初期のライブラリ ・Cray-MPI, IBM-MPI, Intel-MPI, Fujitsu-MPI etc.
*MPICH is pronounced “Em Pee Eye See Aych,” not “Emm Pitch.”, pp. 329, Using MPI, 2nd ed., MIT Press, 1999
23
実装状況(2016年6月)
Implementation Status(MPI3.1), as of June 2016 (http://mpi-forum.org/) c.f. NBC = NonBlocking Collective, RMA = Remote Memory Access, F08 = Fortran2008
24
MPIプログラミングの初歩
1. Hello Wooooorld!
2. プロセス間通信の使用例
25
Hello World!
1 #include <stdio.h> 2 #include “mpi.h” 3 4 int main( int argc, char *argv[] ) 5 { 6 int size, rank; 7 MPI_Init( &argc, &argv ); 8 MPI_Comm_size( MPI_COMM_WORLD, &size ); 9 MPI_Comm_rank( MPI_COMM_WORLD, &rank ); 10 printf( “Hello World! I am %d of %d.¥n”, rank, size ); 11 MPI_Finalize(); 12 return 0; 13 } >mpicc –o hello hello.c >mpiexec –n 2 ./hello Hello World! I am 0 of 2. Hello World! I am 1 of 2. >
hello.c
関数や定数の定義
引数は省略可能
mpirun –np 2 ./hello でも可
26
使用した関数・既定名・コマンド
関数 int MPI_Init(int argc, char ***argv) int MPI_Finalize(void) int MPI_Comm_size(MPI_Comm comm, int *size) int MPI_Comm_rank(MPI_Comm comm, int *rank)
既定名 MPI_COMM_WORLD 全プロセスを含む空間
コマンド mpicc MPI用のCコンパイラ・リンカ
mpiexec MPIプログラム実行コマンド
注: 引数の色は in,out,inout を示す (inは呼び出し前に設定、outは復帰後に値が返される)
27
Hello Worlds!
1 #include <stdio.h> 2 #include “mpi.h” 3 4 int main( int argc, char *argv[] ) 5 { 6 int size, rank; 7 MPI_Init( &argc, &argv ); 8 MPI_Comm_size( MPI_COMM_WORLD, &size ); 9 MPI_Comm_rank( MPI_COMM_WORLD, &rank ); 10 printf( “Hello World A! I am %d of %d¥n”, rank, size ); 11 MPI_Finalize(); 12 return 0; 13 } >mpicc –o helloA helloa.c >mpicc –o helloB hellob.c >mpiexec –n 2 ./helloa : -n 3 ./hellob Hello World A! I am 0 of 5 Hello World A! I am 1 of 5 Hello World B! I am 2 of 5 Hello World B! I am 3 of 5 Hello World B! I am 4 of 5 >
helloa.c
B in hellob.c
mpirun は原則不可
一つのMPI_COMM_WORLDの中に 全プログラムの全プロセスが入る
28
Hello lotta Worlds!
1 # comment 2 -n 2 ./helloa : -n 3 ./hellob 3 -n 1 ./helloc >mpicc –o helloA helloa.c >mpicc –o helloB hellob.c >mpicc –o helloC helloc.c >mpiexec –configfile myconf Hello World A! I am 0 of 6 Hello World A! I am 1 of 6 Hello World B! I am 2 of 6 Hello World B! I am 3 of 6 Hello World B! I am 4 of 6 Hello World C! I am 5 of 6 >
myconf
・構成ファイル(configuration file)を使って実行することもできる*
mpirun は原則不可
改行コードはOSによって異なる 異機種上での実行は要注意
’¥’で継続行にすることも可能
*未実装のライブラリも多い(OpenMPI etc.)
29
MPIプログラミングの初歩
1. Hello Wooooorld!
2. プロセス間通信の使用例
30
数値積分
・台形近似: 関数 の積分を離散点 での値
を用いて と近似する
2
1 + 𝑥2
1
−1
𝑑𝑥 = 𝜋 ・例題: を数値的に求める
𝑓 𝑥 𝑑𝑥 ≅ 1
2𝑓𝑖+1 + 𝑓𝑖 𝑥𝑖+1 − 𝑥𝑖
𝑖
𝑓(𝑥) 𝑓𝑖 𝑥𝑖
𝑥𝑖 𝑥𝑖+1
𝑓𝑖+1
𝑓𝑖
31
1 #include <stdio.h> 2 #include “mpi.h” 3 4 int main( int argc, char *argv[] ) 5 { 6 int size, rank, n, err; 7 MPI_Init( &argc, &argv ); 8 MPI_Comm_size( MPI_COMM_WORLD, &size ); 9 MPI_Comm_rank( MPI_COMM_WORLD, &rank ); 10 11 if( rank==0 ) 12 { 13 printf( “Enter the number of intervals¥n” ); 14 err = scanf( “%d”, &n ); 15 } 16 MPI_Bcast( &n, 1, MPI_INT, 0, MPI_COMM_WORLD ); 17 const double h = 2.0/n; 18 19 double sum = 0.0, pi = 0.0; 20 for( int i=rank; i<n; i+=size ) 21 { 22 const double x0 = i *h – 1.0; 23 const double x1 = (i+1)*h – 1.0; 24 const double f0 = 2.0 / ( 1.0 + x0*x0 ); 25 const double f1 = 2.0 / ( 1.0 + x1*x1 ); 26 sum += 0.5*( f0 + f1 )*( x1 – x0 ); 27 } 28 MPI_Reduce( &sum, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD ); 29 30 if( rank==0 ) 31 printf( “pi is approximately %.16f¥n”, pi ); 32 33 MPI_Finalize(); 34 Return 0; 35 }
trapezoid.c
台形近似
blocking 集団通信
32
>mpicc –o trapeziod trapezoid.c >time mpiexec –n 2 ./trapezoid Enter the number of intervals 10000 pi is approximately 3.1415926469231268 real 0m4.861s user 0m3.804s sys 0m0.044s >
intervals pi -n real 100 3.1415259869232535 2 0m4.036s 1000 3.1415919869231264 4 0m6.651s 10000 3.1415926469231268 8 0m7.685s 100000 3.1415926535231264 16 0m7.333s 1000000 3.1415926535891265 32 0m5.577s ...
実行例
33
MPI_Bcast と MPI_Reduce の動作
MPI_COMM_WORLD
P0
P3 P1
P2
n
n n
n
MPI_Bcast( &n, 1, MPI_INT, 0, … );
ポインタ&nから始まる1つの整数を プロセス0からその他のプロセスに送る
MPI_Reduce( &n, &m, 1, MPI_INT, MPI_SUM, 0, … );
ポインタ&nから始まる1つの整数を プロセス0以外からプロセス0に送り
総和を計算し&mに格納する
MPI_COMM_WORLD
P0
P3 P1
P2
m=Σn
n n
n
n
34
使用した関数等
関数 int MPI_Bcast(void* buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm) int MPI_Reduce(const void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)
データ型・演算 MPI_INT 整数型
MPI_DOUBLE 倍精度実数型
MPI_SUM 加算
35
命名規則(Naming conventions)他
全ての名前は接頭辞 MPI_ で始まる 定数・型・状態・演算等は全て大文字
例: MPI_COMM_WORLD, MPI_INT,
MPI_SUCCESS, MPI_SUM, etc. 関数は最初の一文字のみ大文字、残りは全て小文字
例: MPI_Comm_rank(), MPI_Init(),
MPI_Send(), MPI_Bcast(), etc. 通信が成功した時の戻り値は MPI_SUCCESS
if(MPI_Xxx())は期待通りに動作しない場合あり
失敗したときの値も実装依存 36
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
37
集団通信とは
コミュニケータ内の多プロセス間で一斉に行う通信 一般に要素通信を用いて実装される
注意点
コミュニケータ内の全プロセスで関数呼出しが必要
実際に全てのプロセスが送受信するとは限らない
引数の値は基本的に全てのプロセスで同じ
root, comm は全てのプロセスで同じ値を指定する
送受信バッファはプロセス毎に異なっても良い
関数名にvの付くものはプロセス毎にデータ数が異なる (bcast/scatter/scatterv/gather/gatherv/reduceで 使用されないバッファには MPI_BOTTOM を指定できる)
38
集団通信関数一覧
MPI_Bcast
MPI_Scatter
MPI_Scatterv
MPI_Gather
MPI_Gatherv
MPI_Reduce
MPI_Scan
MPI_Exscan
MPI_Allgather
MPI_Allgatherv
MPI_Alltoall
MPI_Alltoallv
MPI_Alltoallw
MPI_Allreduce
MPI_Reduce_scatter_block
MPI_Reduce_scatter
MPI_Barrier
MPI_Ibcast
MPI_Iscatter
MPI_Iscatterv
MPI_Igather
MPI_Igatherv
MPI_Ireduce
MPI_Iscan
MPI_Iexscan
MPI_Iallgather
MPI_Iallgatherv
MPI_Ialltoall
MPI_Ialltoallv
MPI_Ialltoallw
MPI_Iallreduce
MPI_Ireduce_scatter_block
MPI_Ireduce_scatter
MPI_Ibarrier
Blocking Nonblocking
One to All
All to One
All to All
Some to All
✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
Intercomm MPI_IN_PLACE
39
MPI_Bcast
int MPI_Bcast( void* buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm )
int buf[2];
MPI_Bcast(buf, 2, MPI_INT, 1, MPI_COMM_WORLD);
Bcast
MPI_COMM_WORLD
P0
P1
P2
P3
buf
0 1
buf
0 1
buf
0 1
buf
0 1
MPI_COMM_WORLD
P0
P1
P2
P3
buf
0 1
buf
0 1
buf
0 1
buf
0 1
Function prototype
Example (bcast.c)
• root の持つデータを全プロセスにコピーする
40
MPI_Scatter
int MPI_Scatter( const void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm )
int sbuf[8], rbuf[2];
MPI_Scatter(sbuf, 2, MPI_INT, rbuf, 2, MPI_INT, 1, MPI_COMM_WORLD);
Scatter
MPI_COMM_WORLD
sbuf
0 1 2 3 4 5 6 7
P0
P1
P2
P3
sbuf
0 1 2 3 4 5 6 7
sbuf
0 1 2 3 4 5 6 7
sbuf
0 1 2 3 4 5 6 7
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1
rbuf
0 1
rbuf
0 1
rbuf
0 1
Function prototype
Example (scatter.c)
• root の持つデータを全プロセスに分配する
41
MPI_Scatterv
int MPI_Scatterv( const void* sendbuf, const int sendcounts[], const int displs[], MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm )
int sbuf[11], rbuf[3];
int scounts[4]={1,2,3,2}, displs[4]={0,2,5,9}; MPI_Scatterv(sbuf, scounts, displs, MPI_INT,
rbuf, 3, MPI_INT, 1, MPI_COMM_WORLD);
Scatterv
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1 2 3 4 5 6 7 8 9 10
sbuf
0 1 2 3 4 5 6 7 8 9 10
sbuf
0 1 2 3 4 5 6 7 8 9 10
sbuf
0 1 2 3 4 5 6 7 8 9 10
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1 2
rbuf
0 1 2
rbuf
0 1 2
rbuf
0 1 2
Function prototype
Example (scatterv.c)
42
MPI_Scatter/MPI_Scatterv の注意点
MPI_Scatterv は MPI_Scatter をデータ長可変にしたもの
送信側パラメータ (sendbuf, sendcount, sendcounts[], displs[], sendtype)
• root の値のみ有効、それ以外の値は無視される
受信側パラメータ (recvbuf, recvcount, recvtype)
• recvcount は受信するデータ長以上であれば良い
• recvcount が受信するデータ長未満の場合はエラー
• Scatterv の recvcount は本来プロセス毎に異なる
通常は sendcounts[rank] とする(3でも正常動作)
• recvbuf の配列長は recvcount 以上必要
• recvbuf の不要な配列要素は無視される
• recvtype と sendtype は異なっても良いが
受信データ長が送信より少ないとエラー
分割されたデータの送信先はプロセス番号順
43
MPI_Gather
int MPI_Gather( const void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm )
int sbuf[2], rbuf[8];
MPI_Gather(sbuf, 2, MPI_INT, rbuf, 2, MPI_INT, 1, MPI_COMM_WORLD);
Gather
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1
sbuf
0 1
sbuf
0 1
sbuf
0 1
MPI_COMM_WORLD
rbuf
0 1 2 3 4 5 6 7
P0
P1
P2
P3
rbuf
0 1 2 3 4 5 6 7
rbuf
0 1 2 3 4 5 6 7
rbuf
0 1 2 3 4 5 6 7
Function prototype
Example (gather.c)
• 全プロセスのデータを root に収集する
44
MPI_Gatherv
int MPI_Gatherv( const void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, const int recvcounts[], const int displs[], MPI_Datatype recvtype, int root, MPI_Comm comm )
int sbuf[3], rbuf[11];
int rcounts[4]={1,2,3,2}, displs[4]={0,2,5,9}; MPI_Gatherv(sbuf, 3, MPI_INT,
rbuf, rcounts, displs, MPI_INT, 1, MPI_COMM_WORLD);
Gatherv
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1 2 3 4 5 6 7 8 9 10
rbuf
0 1 2 3 4 5 6 7 8 9 10
rbuf
0 1 2 3 4 5 6 7 8 9 10
rbuf
0 1 2 3 4 5 6 7 8 9 10
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1 2
sbuf
0 1 2
sbuf
0 1 2
sbuf
0 1 2
Function prototype
Example (gatherv.c)
45
MPI_Gather/MPI_Gathervの注意点
MPI_Gatherv は MPI_Gather をデータ長可変にしたもの
送信側パラメータ(sendbuf, sendcount, sendtype)
• Gatherv の sendcount はプロセス毎に異なる
通常は recvcounts[rank] とする
受信側パラメータ (recvbuf, recvcount, recvcounts[], displs[], recvtype)
• root の値のみ有効、それ以外の値は無視される
• recvcount, recvcounts[] は受信するデータ長以上であれば良い
• recvcount, recvcounts[] が受信するデータ長未満の場合はエラー
• recvcounts[] の各要素の値は本来異なる gatherv.cの場合 recvcounts[]={1,2,3,2}でも{3,3,3,3}でも正常に動作
• recvbuf の配列長は (recvcount or recvcounts[])*nproc以上必要
• recvbuf の不要な配列要素は無視される
• recvtype と sendtype は異なっても良いが
受信データのバイト数が不足するとエラー
受信データの開始位置はプロセス番号順
46
MPI_Reduce
int MPI_Reduce( const void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm )
int sbuf[2], rbuf[2];
MPI_Reduce(sbuf, rbuf, 2, MPI_INT, MPI_SUM, 1, MPI_COMM_WORLD);
Reduce
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1
2 5
sbuf
0 1
0 7
sbuf
0 1
6 3
sbuf
0 1
4 1
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1
rbuf
0 1
12 16
rbuf
0 1
rbuf
0 1
12=2+0+6+4
16=5+7+3+1
Function prototype
Example (reduce.c)
• 全プロセスのデータを root に収集して総和
47
MPI_Scan
int MPI_Scan( const void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm )
int sbuf[2], rbuf[2];
MPI_Scan(sbuf, rbuf, 2, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
• 自分を含め自分より番号の若いプロセス上の値の総和
Scan
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1
2 5
sbuf
0 1
0 7
sbuf
0 1
6 3
sbuf
0 1
4 1
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1
2 5
rbuf
0 1
2 12
rbuf
0 1
8 15
rbuf
0 1
12 16
2=2+0
12=5+7
8=2+0+6
15=5+7+3
12=2+0+6+4
16=5+7+3+1
2=2
5=5
Function prototype
Example (scan.c)
48
MPI_Exscan
int MPI_Exscan( const void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm )
int sbuf[2], rbuf[2];
MPI_Exscan(sbuf, rbuf, 2, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
• 自分を除く自分より番号の若いプロセス上の値の総和
Exscan
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1
2 5
sbuf
0 1
0 7
sbuf
0 1
6 3
sbuf
0 1
4 1
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1
-- --
rbuf
0 1
2 5
rbuf
0 1
2 12
rbuf
0 1
8 15
2=2
5=5
2=2+0
12=5+7
8=2+0+6
15=5+7+3
--=--
--=--
Function prototype
Example (exscan.c)
49
MPI_Reduce/MPI_Scan/MPI_Exscanの注意点
受信バッファ(recvbuf) の値
• MPI_Reduce では root のみ値を代入
• MPI_Scan では全プロセスで値を代入
• MPI_Exscan ではプロセス0以外で値を代入
50
int in, out; in = 10; // local array size out = 0; MPI_Exscan( &in, &out, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD ); printf(“rank %d begins from %d¥n”, rank, out);
MPI_Scan/MPI_Exscanの代表的な使い方
大域配列のオフセット値を求める
Process0 Process1 Process2 Process3
0 9 0 9 0 9 0 9
0 9 10 19 20 29 30 39
分散配列
大域配列
>mpiexec –n 4 ./a.out rank 0 begins from 0 rank 1 begins from 10 rank 2 begins from 20 rank 3 begins from 30 >
51
MPI_MAX
MPI_MIN
MPI_MAXLOC
MPI_MINLOC
MPI_SUM
MPI_PROD
MPI_LAND
MPI_LOR
MPI_LXOR
MPI_BAND
MPI_BOR
MPI_BXOR
定義済みの主なデータ型と演算
Integer: MPI_INT, MPI_LONG, MPI_UNSIGNED, etc.
Floating point: MPI_FLOAT, MPI_DOUBLE, etc.
Complex: MPI_C_COMPLEX, MPI_C_DOUBLE_COMPLEX, etc.
Logical: MPI_C_BOOL, etc.
Byte: MPI_BYTE
Integer Floating Complex Logical Byte
✔ ✔ ✔
✔ ✔
✔ ✔
✔ ✔
最大値
最小値
最大値と指標
最小値と指標
総和
総積
論理積
論理和
論理排他和
ビット積
ビット和
ビット排他和
52
struct{ float value; int index; } in[2], out[2]; in[0].value = ...; in[0].index = ...; in[1].value = ...; in[1].index = ...; MPI_Reduce( in, out, 2, MPI_FLOAT_INT, ¥ MPI_MAXLOC, 1, MPI_COMM_WORLD );
MPI_MAXLOC/MPI_MINLOCの使い方
in[0].value in[0].index in[1].value in[1].index Process0 1.2 3 4.2 9 Process1 3.5 4 1.3 2 Process2 2.3 1 8.4 7 Process3 4.8 0 5.7 3 out[0].value out[0].index out[1].value out[1].index Process1 4.8 0 8.4 7
例
プロセス1には以下の値が格納される
最大の実数値とその指標を探す
• 指標は必ず整数 • valueが整数の場合はMPI_2INT型(MPI_INT_INTではない)
Example (maxloc.c)
53
ユーザ定義演算
typedef struct{ float vx, vy; } fvec2;
void addvec(void *invec, void* iovec, int *len, MPI_Datatype *dptr) { fvec2 *a = (fvec2 *)invec; // in vector fvec2 *b = (fvec2 *)iovec; // inout vector for(int i=0; i<*len; ++i) { b->vx += a->vx; b->vy += a->vy; ++a; ++b; } }
MPI_Datatype vtype; MPI_Type_contiguous(2, MPI_FLOAT, &vtype); MPI_Type_commit(&vtype);
MPI_Op myOp; MPI_Op_create(addvec, 1, &myOp);
fvec2 in[4], sum[4]; MPI_Reduce(in, sum, 4, vtype, myOp, 1, MPI_COMM_WORLD);
MPI_Op_free(&myOp);
2次元ベクトル合成
戻り値なし
可換演算は true (実装依存)を指定
fvec2をMPI_Datatypeに変換
演算本体(結果を iovec 側に格納する)
Example (reduceop.c)
dptrは省略不可
この例では4が代入される
54
MPI_Allgather
int MPI_Allgather( const void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm )
int sbuf[2], rbuf[8];
MPI_Allgather(sbuf, 2, MPI_INT, rbuf, 2, MPI_INT, MPI_COMM_WORLD);
Allgather
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1
sbuf
0 1
sbuf
0 1
sbuf
0 1
MPI_COMM_WORLD
rbuf
0 1 2 3 4 5 6 7
P0
P1
P2
P3
rbuf
0 1 2 3 4 5 6 7
rbuf
0 1 2 3 4 5 6 7
rbuf
0 1 2 3 4 5 6 7
Function prototype
Example (allgather.c)
• 引数はGatherとほぼ同じ(rootが無い)、動作はGather+Bcast
55
MPI_Allgatherv
int MPI_Allgatherv( const void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, const int recvcounts[], const int displs[], MPI_Datatype recvtype, MPI_Comm comm )
int sbuf[3], rbuf[11];
int rcounts[4]={1,2,3,2}, displs[4]={0,2,5,9}; MPI_Allgatherv(sbuf, 3, MPI_INT,
rbuf, rcounts, displs, MPI_INT, MPI_COMM_WORLD); 注:rcounts, displsはrootの値だけ有効、sendcountの値はプロセス毎に異なる(この例ではプロセス2)
・引数はGathervとほぼ同じ(rootが無い)、動作はGatherv+Bcast
Allgatherv
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1 2 3 4 5 6 7 8 9 10
rbuf
0 1 2 3 4 5 6 7 8 9 10
rbuf
0 1 2 3 4 5 6 7 8 9 10
rbuf
0 1 2 3 4 5 6 7 8 9 10
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1 2
sbuf
0 1 2
sbuf
0 1 2
sbuf
0 1 2
Function prototype
Example (allgatherv.c)
56
MPI_Allreduce
int MPI_Allreduce( const void* sendbuf, void* recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm )
int sbuf[2], rbuf[2];
MPI_Allreduce(sbuf, rbuf, 2, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
• 引数はReduceとほぼ同じ(rootが無い)
• 動作はReduce+Bcast
Allreduce
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1
2 5
sbuf
0 1
0 7
sbuf
0 1
6 3
sbuf
0 1
4 1
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1
12 16
rbuf
0 1
12 16
rbuf
0 1
12 16
rbuf
0 1
12 16
12=2+0+6+4
16=5+7+3+1
12=2+0+6+4
16=5+7+3+1
12=2+0+6+4
16=5+7+3+1
12=2+0+6+4
16=5+7+3+1
Function prototype
Example (allreduce.c)
57
MPI_Reduce_scatter_block
int MPI_Reduce_scatter_block( const void* sendbuf, void* recvbuf, int recvcount, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm )
int sbuf[8], rbuf[2];
MPI_Scatter_block(sbuf, rbuf, 2, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
• 引数はReduceとほぼ同じ(rootが無い)
• 動作はReduce+Scatter(sumにReduceしてScatter)
Reduce _scatter
_block
MPI_COMM_WORLD
sbuf 0 4 2 5 1 6 3 7
0 1 2 3 4 5 6 7
P0
P1
P2
P3
sbuf 2 3 0 1 4 6 5 7
0 1 2 3 4 5 6 7
sbuf 5 7 6 2 3 0 1 4
0 1 2 3 4 5 6 7
sbuf 7 0 1 4 5 3 6 2
0 1 2 3 4 5 6 7
MPI_COMM_WORLD
P0
P1
P2
P3
rbuf
0 1
14 15
rbuf
0 1
12 14
rbuf
0 1
20 9
rbuf
0 1
13 15
sum
14 15 12 14 20 9 13 15
14=2+0+5+7
15=5+3+1+6
12=1+5+2+4
14=3+4+7+0
20=7+7+4+2
9=0+2+6+1
13=4+1+3+5
15=6+6+0+3
Function prototype
Example (reducescatterblock.c)
58
MPI_Reduce_scatter
int MPI_Reduce_scatter( const void* sendbuf, void* recvbuf, const int recvcounts[], MPI_Datatype datatype, MPI_Op op, MPI_Comm comm )
int sbuf[8], rbuf[3];
int rcounts[4]={1,2,3,2}; MPI_Scatter(sbuf, rbuf, rcounts, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
• Reduceとほぼ同じ(引数にrootが無い、recvcountが配列)
• 動作はReduce+Scatterv(sumにReduceしてScatterv)
Reduce _scatter
MPI_COMM_WORLD
sbuf 0 4 2 5 1 6 3 7
0 1 2 3 4 5 6 7
P0
P1
P2
P3
sbuf 2 3 0 1 4 6 5 7
0 1 2 3 4 5 6 7
sbuf 5 7 6 2 3 0 1 4
0 1 2 3 4 5 6 7
sbuf 7 0 1 4 5 3 6 2
0 1 2 3 4 5 6 7
sum
14 15 12 14 20 9 13 15
14=2+0+5+7
15=5+3+1+6
12=1+5+2+4
14=3+4+7+0
20=7+7+4+2
9=0+2+6+1
13=4+1+3+5
15=6+6+0+3
MPI_COMM_WORLD
P0
P1
P2
P3
sbuf
0 1 2
14
sbuf
0 1 2
15 12
9 sbuf
0 1 2
14 20
sbuf
0 1 2
13 15
Function prototype
Example (reducescatter.c)
59
MPI_Alltoall
int MPI_Alltoall( const void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm )
int sbuf[8], rbuf[8];
MPI_Alltoall(sbuf, 2, MPI_INT, rbuf, 2, MPI_INT, MPI_COMM_WORLD);
• 引数はGatherとほぼ同じ(rootが無い)
• 動作はプロセス毎にsbufの開始位置を変えてGather, またはプロセス毎にrbufの開始位置を変えてScatter
• sbuf, rbufに十分な大きさがあればsendcount!=recvcountも可能(intercommの場合など)
Alltoall
MPI_COMM_WORLD
sbuf
0 1 2 3 4 5 6 7
sbuf
0 1 2 3 4 5 6 7
sbuf
0 1 2 3 4 5 6 7
sbuf
0 1 2 3 4 5 6 7
P0
P1
P2
P3
MPI_COMM_WORLD
rbuf
0 1 2 3 4 5 6 7
rbuf
0 1 2 3 4 5 6 7
rbuf
0 1 2 3 4 5 6 7
rbuf
0 1 2 3 4 5 6 7
P0
P1
P2
P3
Function prototype
Example (alltoall.c)
60
MPI_Alltoallv
int MPI_Alltoallv( const void* sendbuf, const int sendcounts[], const int sdispls[], MPI_Datatype sendtype, void* recvbuf, const int recvcounts[], const int rdispls[], MPI_Datatype recvtype, MPI_Comm comm )
scounts rcounts Process0 { a0, a1, a2, a3 } { a0, b0, c0, d0 } Process1 { b0, b1, b2, b3 } { a1, b1, c1, d1 } Process2 { c0, c1, c2, c3 } { a2, b2, c2, d2 } Process3 { d0, d1, d2, d3 } { a3, b3, c3, d3 }
• scounts, rcountsの値は互いに転置関係
scounts, sdispls, rcounts, rdisplsの値は整合的でなければならない
• sendbuf, recvbufには十分な大きさが必要
int sendbuf[ssize]; // ssize > sdispls[0]+sdispls[1]+...
int recvbuf[rsize]; // rsize > rdispls[0]+rdispls[1]+...
• 引数はAlltoallとほぼ同じ(sendcount, recvcountが配列)
• 動作は各プロセスがsbufとrbufの開始位置を変えてGather
Function prototype
61
int sbuf[13], rbuf[13];
int scounts[4], sdispls[4], rcounts[4], rdispls[4]; MPI_Alltoallv(sbuf, scounts, sdispls, MPI_INT,
rbuf, rcounts, rdispls, MPI_INT, 1, MPI_COMM_WORLD);
scounts sdispls rcounts rdispls Process0 { 2, 1, 3, 2 } { 0, 2, 4, 8 } { 2, 1, 2, 3 } { 0, 3, 5, 9 } Process1 { 1, 2, 2, 3 } { 1, 3, 5, 7 } { 1, 2, 3, 2 } { 2, 4, 7,11 } Process2 { 2, 3, 1, 2 } { 2, 5, 8,11 } { 3, 2, 1, 2 } { 0, 4, 8,10 } Process3 { 3, 2, 2, 1 } { 2, 6, 9,12 } { 2, 3, 2, 1 } { 1, 3, 6, 9 }
• プロセス数が4で上記のパラメータの場合
Alltoallv
MPI_COMM_WORLD
sbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
sbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
sbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
sbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
P0
P1
P2
P3
MPI_COMM_WORLD
rbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
rbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
rbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
rbuf
0 1 2 3 4 5 6 7 8 9 10 11 12
P0
P1
P2
P3
Example (alltoallv.c)
62
MPI_Alltoallw
int MPI_Alltoallw( const void* sendbuf, const int sendcounts[], const int sdispls[], const MPI_Datatype sendtypes[], void* recvbuf, const int recvcounts[], const int rdispls[], const MPI_Datatype recvtypes[], MPI_Comm comm )
プロセス毎に異なったDatatype
sendbuf, recvbufには十分な大きさが必要
sdispls, rdisplsはバイト数で指定する
• 引数はAlltoallvとほぼ同じ(sendtypes, recvtypesが配列)
sdispls[0] = rdispls[0] = 0; for(int i=0; i<size-1; ++i) { sdispls[i+1] = sdispls[i] + sizeof(sendtypes[i])*nsendcounts[i]; rdispls[i+1] = rdispls[i] + sizeof(recvtypes[i])*nrecvcounts[i]; }
Function prototype
Example (alltoallw.c)
63
MPI_Barrier
int MPI_Barrier( MPI_Comm comm )
全プロセスがコールするまで待ち合わせる
全プロセスが同時に復帰する訳ではない(タイムラグ有り)
ハードウェアによって高速化している場合もある
MPI_Barrier(MPI_COMM_WORLD);
Function prototype
Example
64
int MPI_Reduce_local( const void* inbuf, void* inoutbuf, int count, MPI_Datatype datatype, MPI_Op op )
引数にrootとMPI_Commが無い、プロセス内のみでreduce
int ibuf[2], iobuf[2];
MPI_Reduce_local(ibuf, iobuf, 2, MPI_INT, MPI_SUM );
int MPI_Op_commutative (MPI_Op op, int *commute )
演算が可換か否かを返す(true or false)
int iscommutative;
MPI_Op_commutative(MPI_INT, &iscommutative );
その他の関数 Function prototype
Function prototype
Example
Example
65
非閉塞(Nonblocking)通信
通信相手の状態に関係なく即座に復帰
後からTest/Wait関数で完了
ノード内に通信用コプロセッサ等を持つ場合には Linuxカーネルを改造し通信処理を荷卸し(off-load) することで通信の隠蔽(overwrap)が可能な場合あり*
荷卸しできない場合はTest/Wait関数の中で 通信を開始するので通信は隠蔽できない (multi-thread化すれば隠蔽可能)
非閉塞通信は本来デッドロックを避けるために導入 MPI standardの中に隠蔽に関する規定は無い
*With suitable hardware, … may proceed concurrently with computations ... , pp. 47, MPI Standard 3.1
*whether such overlapping is possible may depend on the hardware environment, pp. 109, Using MPI, 2nd ed., MIT Press, 1999
66
int buf[3] = ... , sbuf[2] = ... , rbuf[2] = ... ; // buffers MPI_Request req[2]; MPI_Ibcast( buf, 3, MPI_INT, MPI_COMM_WORLD, &req[0] ); // some workloads MPI_Ireduce( sbuf, rbuf, 2, MPI_INT, MPI_SUM, 1, MPI_COMM_WORLD, &req[1] ); // another workloads MPI_Waitall(2, req, MPI_STATUSES_IGNORE);
非閉塞通信の使い方
IbcastとIreduceを使う場合
即座に復帰
待ち合せて完了
閉塞通信より引数が一つ(&req[ ])増えただけ
Waitall が復帰するまで buf, sbuf, rbuf にアクセスできない
その他の集団通信の使い方も上記と同じ
Wait, Testについては要素通信の中で説明する
即座に復帰
集団通信でstatusは不要
Example
67
送受信バッファの共通化
送信バッファにMPI_IN_PLACEを指定するもの rootの指定のみ有効
MPI_Gather, MPI_Gatherv, MPI_Reduce 全プロセスで指定必要
MPI_Scan, MPI_Exscan, MPI_Allgather, MPI_Allgatherv, MPI_Allreduce, MPI_Alltoall, MPI_Alltoallv, MPI_Alltoallw, MPI_Reduce_scatter_block, MPI_Reduce_scatter
受信バッファにMPI_IN_PLACEを指定するもの rootの指定のみ有効
MPI_Scatter, MPI_Scatterv
非対応 MPI_Bcast , MPI_Barrier, MPI_Reduce_local, MPI_Op_commutative
非閉塞通信も閉塞通信と全く同じ メモリ節約に有効だが、同一アドレスへのread/write発生により効率面で不利 C言語ではポインタを使えばメモリコピー不要で配列入替えが可能
68
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
69
要素通信とは
最も基本的な通信方法 コミュニケータ内の一対のプロセス間の通信 一方が送信し、他方が受信する 送信側が制御する push mechanism 送受信を一つにまとめた複合通信が用意されている オーバーヘッドが軽い持続通信が用意されている
注意点
送信側にはモードがある 不適切なモード選択はデッドロックを引き起こす
70
要素通信関数一覧
✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
MPI_Send
MPI_Ssend
MPI_Bsend
MPI_Rsend
MPI_Recv
MPI_Mrecv*
MPI_Probe
MPI_Mprobe*
MPI_Sendrecv
MPI_Sendrecv_replace
MPI_Send_init
MPI_Ssend_init
MPI_Bsend_init
MPI_Rsend_init
MPI_Recv_init
MPI_Isend
MPI_Issend
MPI_Ibsend
MPI_Irsend
MPI_Irecv
MPI_Imrecv*
MPI_Iprobe
MPI_Improbe*
MPI_Start
MPI_Startall
Blocking Nonblocking
Send
Recv
Probe
Intercomm
✔ ✔ ✔ ✔
✔ ✔
✔ ✔
✔ ✔ ✔ ✔ ✔
Persistent
Combined
71 * MPI_Mrecv, MPI_Mprobe, MPI_Imrecv, MPI_Improbe はマルチスレッドで説明する
要素通信関数一覧(続)
MPI_Wait
MPI_Waitany
MPI_Waitall
MPI_Waitsome
Buffer
Complete
MPI_Test
MPI_Testany
MPI_Testall
MPI_Testsome
Request
Status
MPI_Cancel
MPI_Request_free
MPI_Request_get_status
MPI_Test_cancelled
MPI_Get_count
MPI_Get_element*
MPI_Get_element_x*
MPI_Buffer_attach
MPI_Buffer_detach
72
Blocking Nonblocking
* MPI_Get_element, MPI_Get_element_x の説明は省略
MPI_Send
int MPI_Send( const void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm )
int buf[2];
MPI_Send(buf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD);
MPI_Recv
int MPI_Recv( void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status )
int buf[2];
MPI_Status status; MPI_Recv(buf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD, &status );
bufから始まる2つの整数にタグ9を付けてプロセス1に送る
タグ9が付いた2つの整数をプロセス0から受け取りbufに格納する
タグは通信の順序を識別するためにユーザが付ける整数 statusはMPI_SOURCE, MPI_TAG, MPI_ERRORからなる構造体 source/dest, tag, commの三つ組みをエンベロープ(envelope)と呼ぶ
73
Function prototype
Function prototype
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Send( sbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD ); } else if(rank==1) MPI_Recv( rbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
2プロセスの例
Proc0からProc1にデータを送る
注:statusは必要ないのでMPI_STATUS_IGNOREで無効にしている
① Proc0がSendを開始
② Proc1がRecvを開始
③ Proc1のRecvが復帰(完了)
④ Proc0のSendが復帰(完了)
Proc0 Proc1
Send Recv
正常終了
① ②
③ ④
74
Example (sendrecv01.c)
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Send( sbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 20; sbuf[1] = 30; MPI_Recv( rbuf, 2, MPI_INT, 0, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); MPI_Send( sbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD ); }
Proc0とProc1がデータを交換する(その1)
Proc0 Proc1
Send Recv
正常終了
① ②
③
Recv Send
④ ⑤ ⑥
⑦ ⑧
① Proc0がSendを開始
② Proc1がRecvを開始
③ Proc1のRecvが復帰(完了)
④ Proc0のSendが復帰(完了)
⑤ Proc1がSendを開始
⑥ Proc0がRecvを開始
⑦ Proc0のRecvが復帰(完了)
⑧ Proc1のSendが復帰(完了)
75
Example (sendrecv02.c)
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Recv( rbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); MPI_Send( sbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD ); } else if(rank==1) { sbuf[0] = 20; sbuf[1] = 30; MPI_Recv( rbuf, 2, MPI_INT, 0, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); MPI_Send( sbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD ); }
Proc0とProc1がデータを交換する(その2)
① Proc0がRecvを開始
② Proc1がRecvを開始
③ 相手側がSendを開始するまで Recvは復帰できない ④ Recvが復帰しないと
Sendを開始できない
Proc0 Proc1
Recv Recv
② ①
互いに相手を待ち続ける
76
Example (sendrecv03.c)
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Send( sbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 20; sbuf[1] = 30; MPI_Send( sbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 0, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); }
Proc0とProc1がデータを交換する(その3)
① Proc0がSendを開始
② Proc1がSendを開始
③ Proc0がデータをバッファリング
④ Proc1がデータをバッファリング
⑤ Proc0のSendが復帰
⑥ Proc1のSendが復帰
⑦ Proc0がRecvを開始
⑧ Proc1がRecvを開始
⑨ Proc0のRecvが復帰 ⑩ Proc1のRecvが復帰 正常終了
システムバッファ
Proc0 Proc1
Send Send ① ② ④
Recv Recv
③
⑥ ⑤
⑨ ⑩
⑧ ⑦
データが小さい場合(<16KB)
バッファリング閾値は実装依存 !! 77
Example (sendrecv04.c)
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Send( sbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 20; sbuf[1] = 30; MPI_Send( sbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 0, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); }
Proc0とProc1がデータを交換する(その4)
① Proc0がSendを開始
② Proc1がSendを開始
③ 相手側がRecvを開始するまで Sendは復帰できない ④ Sendが復帰しないと
Recvを開始できない
データが大きい場合(>16KB) Proc0 Proc1
Send Send
② ①
互いに相手を待ち続ける
78
Example (sendrecv05.c)
通信モード
送信関数には4種類のモードがある
• Standard mode
MPI_Send, MPI_Isend, MPI_Send_init
• Synchronous mode
MPI_Ssend, MPI_Issend, MPI_Ssend_init
• Ready mode
MPI_Rsend, MPI_Irsend, MPI_Rsend_init
• Buffered mode
MPI_Bsend, MPI_Ibsend, MPI_Bsend_init
MPI_Recv, MPI_Irecv, MPI_Recv_init
受信関数にはモードがない (push mechanism)
79
• Standard データが小さい場合はシステムバッファにバッファリング
データが大きい場合は受信側とハンドシェイク
• Synchronous バッファリングせず受信側とハンドシェイク
最もデッドロック起き易い
このモードでデッドロックしないプログラムはsafe programと呼ばれる
• Ready
受信側の状態に関係なく即座に送信
受信側が既に待機している場合のみ開始可能
それ以外はエラーまたは不定状態
• Buffered
ユーザが設定した添付バッファにバッファリング
各モードの特徴
80
• MPICHでは2つの通信プロトコルを定義*
• Rendezvous protocol まずエンベロープだけ送り、準備整ったらデータを送信
• Eager protocol エンベロープとデータを続けて即座に送信
• 送信関数の4つのモードで使い分けている
• Standard (Rendezvous or Eager with system-buffer)
• Synchronous (Rendezvous always without system-buffer)
• Ready (Eager always without system-buffer)
• Buffered (Rendezvous with attached-buffer)
*MPI standardに規定はない
通信モードの実装方法
81
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Bsend( sbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 20; sbuf[1] = 30; MPI_Ssend( sbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 0, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); }
① Proc0がBsendを開始
② Proc1がSsendを開始
③ 添付バッファにバッファリング
④ Proc0がBsendから復帰
⑤ Proc0がRecvを開始 ⑥ Proc1がSsendから復帰
⑦ Proc1がRecvを開始
⑧ Proc0がRecvから復帰
⑨ Proc1がRecvから復帰 正常終了
• プロセス0でMPI_SendをMPI_Bsendに置き換える. • プロセス1はバッファリングを抑止するためMPI_Ssendを使う.
Proc0 Proc1
Bsend Ssend
① ②
Recv Recv
③
⑥ ④
⑧ ⑨
⑦ ⑤
通信モードを使ってデッドロックを避ける
82
Example (mode01.c)
MPI_Buffer_attach/MPI_Buffer_detach/ MPI_Bsend/MPI_Ssend
int MPI_Buffer_attach( void* buffer, int size ) int MPI_Buffer_detach( void* buffer_addr, int* size ) int MPI_Bsend( const void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm )
#define BUFFER_SIZE 10000 int bufsize = sizeof(int)*BUFFER_SIZE; void *buf = malloc((size_t)bufsize); MPI_Buffer_attach(buf, bufsize); if(rank==0) MPI_Bsend(buf, 5000, MPI_INT, 1, 9, MPI_COMM_WORLD); else if(rank==1) MPI_Recv(buf, 5000, MPI_INT, 0, 9, MPI_COMM_WORLD); MPI_Buffer_detach(buf, &bufsize);
ユーザ定義のバッファを生成・解放する
注:バッファサイズの指定はバイト数! Buffer_detachのsizeはポインタ型!
83
Function prototype
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Send( sbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 2, 7, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 30; sbuf[1] = 40; MPI_Send( sbuf, 2, MPI_INT, 2, 8, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==2) { sbuf[0] = 50; sbuf[1] = 60; MPI_Send( sbuf, 2, MPI_INT, 0, 7, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); }
正常終了できるかはバッファリングに依存
3プロセスのデータ交換
周期境界の場合
Buffered modeを使う
Proc0 Proc1 Proc2
Send
Recv
Send
Recv
Send
Recv
84
Example (mode02.c)
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Bsend( sbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 2, 7, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 30; sbuf[1] = 40; MPI_Ssend( sbuf, 2, MPI_INT, 2, 8, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==2) { sbuf[0] = 50; sbuf[1] = 60; MPI_Ssend( sbuf, 2, MPI_INT, 0, 7, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); }
添付バッファサイズが十分なら 正常終了
• プロセス0でMPI_SendをMPI_Bsendに置き換える. • プロセス1と2はバッファリングを抑止するためMPI_Ssendを使う.
① tag=7の送受信が完了
② tag=8の送受信が完了
③ tag=9の送受信が完了 しかし送受信は逐次的 ⇒ 非効率
int MPI_Ssend( const void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm )
85
Example (mode03.c)
Function prototype
int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Bsend( sbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 2, 7, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 30; sbuf[1] = 40; MPI_Bsend( sbuf, 2, MPI_INT, 2, 8, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==2) { sbuf[0] = 50; sbuf[1] = 60; MPI_Bsend( sbuf, 2, MPI_INT, 0, 7, MPI_COMM_WORLD ); MPI_Recv( rbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); }
添付バッファサイズが十分なら 正常終了
• 全てのプロセスでMPI_SendをMPI_Bsendに置き換える..
通信は並列に実行可能 しかしメモリコピーの時間が必要
データサイズが大きな場合非効率
⇒ 非閉塞Synchronous通信を使う! 86
Example (mode04.c)
正常終了(並列的・メモリコピー不要)
MPI_Issend
MPI_Request req; int sbuf[2], rbuf[2]; if(rank==0) { sbuf[0] = 10; sbuf[1] = 20; MPI_Issend( sbuf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD, &req ); MPI_Recv( rbuf, 2, MPI_INT, 2, 7, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==1) { sbuf[0] = 30; sbuf[1] = 40; MPI_Issend( sbuf, 2, MPI_INT, 2, 8, MPI_COMM_WORLD, &req ); MPI_Recv( rbuf, 2, MPI_INT, 0, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } else if(rank==2) { sbuf[0] = 50; sbuf[1] = 60; MPI_Issend( sbuf, 2, MPI_INT, 0, 7, MPI_COMM_WORLD, &req ); MPI_Recv( rbuf, 2, MPI_INT, 1, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); } MPI_Wait( &req, MPI_STATUS_IGNORE );
• 全てのプロセスでMPI_SendをMPI_Issendに置き換える. • 後からMPI_Waitで完了させる.
但しリクエストの完了が必要
int MPI_Issend( const void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm , MPI_Request *request )
87
Example (mode05.c)
Function prototype
88
完了関数の基本動作
• 閉塞通信では復帰=完了なので不要 • 非閉塞通信(持続通信も含む)では 通信ハンドル(request)を生成して即座に復帰する • 通信の完了には request を引数にWaitまたはTest関数を呼ぶ • 通信が完了していれば request から status が生成される • recv側は MPI_Get_count で status から受信データ数を得る • send側の request は MPI_request_free ですぐに解放しても良い
Wait関数 (MPI_Wait, MPI_Waitall, MPI_Waitany, MPI_Waitsome)
• Wait は request の完了まで待ち、完了後に status を返す • Wait の動作は非局所的(他プロセスに依存)で blocking
Test関数 (MPI_Test, MPI_Testall, MPI_Testany, MPI_Testsome) • Test は request の状態を flag に返して復帰する • status の値は flag=true の時には正常値、 flag=false の時は不定値となる • Test の動作は局所的(自プロセスで完結)で nonblocking
int MPI_Request int int int MPI_Status MPI_Wait ( ----- request, ----- ----- ----- *status ) MPI_Test ( ----- request, ----- *flag, ----- *status ) MPI_Wait_all ( count, requests[], ----- ----- ----- statuses[] ) MPI_Test_all ( count, requests[], ----- *flag, ----- statuses[] ) MPI_Wait_any ( count, requests[], ----- ----- *index, *status ) MPI_Test_any ( count, requests[], ----- *flag, *index, *status ) MPI_Wait_some ( incount, requests[], *outcount, ----- indices[], statuses[] ) MPI_Test_some ( incount, requests[], *outcount, ----- indices[], statuses[] )
89
完了関数の引数一覧
• all は requests[0, …, count-1]全てについて Wail/Test • any は requests[0, …, count-1]のどれか一つ(indexにその番号を返す) • some は requests[0, …, incount-1]の内 *outcount 個、番号は indices • Test_some は all, any と仕様が違うので注意 • 既に完了した request を再度 Wait, Test しても問題ない
• 非閉塞集団通信の場合の status の内容は不定(記述無し)
90
完了関数の引数一覧(続き)
• Cancel は request を取り消すが、その後に MPI_Wait か MPI_Test で完了するか、MPI_Request_free で解放する必要がある • 非閉塞の要素通信は cancel できるが、集団通信はできない • Cancel は重い処理なのでなるべく使用しない • Request_free は request の割当てを解除(deallocate)する 解放された request は MPI_Wait や MPI_Test の引数に指定できない • Request_get_status は request が完了したかどうかを調べ 結果を flag と status に返す
MPI_Request int MPI_Status MPI_Cancel ( *request, ----- ----- ) MPI_Request_free ( *request, ----- ----- ) MPI_Request_get_status ( request, *flag, *status )
const MPI_Status int MPI_Test_cancelled ( *status, *flag )
• statusを調べて対応する通信が cancel に成功したかを flag に返す
複合通信 (Combined communication)
int MPI_Sendrecv ( const 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 ) int MPI_Sendrecv_replace ( void *buf, int count, MPI_Datatype datatype, int dest, int sendtag, int source, int recvtag, MPI_Comm comm, MPI_Status *status )
91
Function prototype
自プロセス内の送信と受信を一つの関数で実行する
一対の送受信ではないことに注意
通信相手側は通常の send, recv で受けてることもできる
バッファを共通化した関数もある
閉塞通信のみ
int sbuf[2], rbuf[2], buf[2]; int left, right; MPI_sendrecv( sbuf, 2, MPI_INT, left, 9, rbuf, 2, MPI_INT, right, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); MPI_sendrecv_replace( buf, 2, MPI_INT, left, 9, right, 8, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
Example (-----.c)
• replaceではデータ型とサイズは送信と受信で同じでなければならない
int MPI_Send_init( const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request ) int MPI_Ssend_init( const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request ) int MPI_Bsend_init( const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request ) int MPI_Rsend_init( const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request ) int MPI_Recv_init( const void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request ) int MPI_Start( MPI_Request *request ) int MPI_Startall( int count, MPI_Request array_of_requests [] )
持続通信 (Persistent communication)
92
Function prototype
最初に作った request を何度も再利用できる
通信開始は request を start させるだけでよい
非閉塞通信と同様に test, wait を呼んで完了させる
93
int sbuf[100], rbur[100]; MPI_Request req[2]; MPI_Send_init( sbuf, 100, MPI_INT, left, 9, MPI_COMM_WORLD, &(req[0]) ); MPI_Recv_init( rbuf, 100, MPI_INT, left, 8, MPI_COMM_WORLD, &(req[1]) ); for(int n=0; n<NSTEP; ++n) { // setup sbuf
MPI_Startall( 2, req );
// overlap calculations
MPI_Waitall( 2, req, MPI_STATUSES_IGNORE );
// read rbuf }
Example (-----.c)
データを一斉に隣のプロセスに送るシフト通信の場合 両端のプロセスは送信または受信のみとなる
送受信関数の dest/source に MPI_PROC_NULL を指定すると
通信せずに復帰する
これを使うとシフト通信のプログラムが少し見易くなる
MPI_PROC_NULLを使う
int sbuf[10], rbuf[10]; int size, rank; MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); int dest = ( rank<size-1 ? rank+1 : MPI_PROC_NULL ); int src = ( rank>0 ? rank-1 : MPI_PROC_NULL );
MPI_Send( sbuf, 10, MPI_INT, dest, 1, MPI_COMM_WORLD ); MPI_Recv( rbuf, 10, MPI_INT, src , 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
Proc0 Proc1 Proc2
Send
Recv
Send
Recv
Send
Recv
MPI_PROC_NULL
MPI_PROC_NULL 94
マスターワーカモデル(Master-Worker model)では各ワーカがマスターに送ってくるタイミングが不定
プロセス番号とタグを指定して受信する方法では無駄な待ちが発生 ⇒ 順不同にデータを受け取りたい
受信関数の source, tag に MPI_ANY_SOURCE, MPI_ANY_TAG を指定すると到着した順(First come, first served)に受信する
countは実際に受信するデータより大きな値でも可
しかし送信されたデータのサイズが予め分らないので 受信バッファ rbuf のサイズを決められない
不足すると segmentation fault 等を発生しエラー終了
⇒ MPI_Probe で事前に status のみ取得する!
MPI_ANY_SOURCE/MPI_ANY_TAGを使う
int rbuf[100]; int count; if( master ) MPI_Recv( rbuf, count, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
95
MPI_Probe はデータは受け取らず status のみ受け取る
MPI_Iprobeはその非閉塞版、flagはtrueまたはfalseを返す
MPI_Get_countは status から受信データのサイズを抽出する
MPI_Probe/MPI_Iprobe/MPI_Get_count
int MPI_Probe( int source, int tag, MPI_Comm comm , MPI_Status *status ) int MPI_Iprobe( int source, int tag, MPI_Comm comm , int *flag, MPI_Status *status ) int MPI_Get_count( const MPI_Status *status, MPI_Datatype datatype, int *count ) Int MPI_Alloc_mem( MPI_Aint size, MPI_Info info, void *baseptr )
int *rbuf; int count; MPI_Status status; if( master ) { MPI_Probe( MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status ); MPI_Get_count( &status, MPI_INT, &count ); MPI_Alloc_mem( count*sizdof(int), MPI_INFO_NULL, rbuf ); MPI_Recv( rbuf, count, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE ); }
注:statusはMPI_SOURCE, MPI_TAG, MPI_ERROR等をメンバとして持つ構造体
Probe / Iprobeはマルチスレッド実行時に問題あり 96
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
97
片側通信とは
各プロセスのメモリ上にウィンドウ(window)と呼ばれる公開領域を設定、これの領域に対して送受信する
注意点
ウィンドウ初期化、送受信、同期の3ステップがある
送受信関数の呼び出しは送信側または受信側のみ
比較・演算を組合わせた送受信も可能
送受信関数には request を返すものもある
通信は request が無くても同期が必要
同期方法には2種類の能動的同期(active target
synchronization)と1種類の受動的同期(passive target
synchronization)がある
98
片側通信関数一覧
Nonlocal Local
Communi-
cation
Initialization
MPI_Rput
MPI_Rget
MPI_Raccumurate
MPI_Rget_accumurate
MPI_Put
MPI_Get
MPI_Accumurate
MPI_Get_accumurate
MPI_Fetch_and_op
MPI_Compare_and_swap
99
MPI_Win_create
MPI_Win_allocate
MPI_Win_allocate_shared*
MPI_Win_create_dynamic
MPI_Win_free
MPI_Win_attach
MPI_Win_detach
* MPI_Win_allocate_shared, MPI_Win_shared_query はマルチスレッドで説明する
Query MPI_Win_shared_query*
片側通信関数一覧(続)
Synchro-
nization
100
MPI_Win_Fence
MPI_Win_Start
MPI_Win_Post
MPI_Win_Complete
MPI_Win_Wait
MPI_Win_Test
Active target Passive target
MPI_Win_lock
MPI_Win_lock_all
MPI_Win_unlock
MPI_Win_unlock_all
MPI_Win_flush
MPI_Win_flush_all
MPI_Win_flush_local
MPI_Win_flush_local_all
MPI_Win_sync
Group MPI_Win_get_group*
Info MPI_Win_set_info*
MPI_Win_get_info*
* MPI_Win_get_group, MPI_Win_set_info, MPI_Win_get_info の説明は省略
101
初期化関数の引数
• どちらも local call、size はバイト数指定 • base, size はプロセス毎に異なってもよい
• MPI_Win_detach の引数 *base は正確には const void
MPI_Win void MPI_Aint MPI_Win_attach ( win *base size ) MPI_Win_detach ( win *base ----- )
• 全て collective call である、size と disp_unit はバイト数を指定する • base, size, disp_unit, info はプロセス毎に異なってもよい • MPI_Win_create は base から始まる size バイトの領域を設定する
size==0 の場合は base に MPI_BOTTOM を指定してもよい
• MPI_Win_allocate はシステムが size バイトの領域を確保、その先頭番地を
baseptr で返す(symmetric allocation できれば create より scalability 良い可能性)
• MPI_Win_create_dynamic は領域を確保せずにウィンドウを作成する
通信する前に MPI_Win_attach でメモリ割当てが必要
MPI_Win_detach で解放してから再度割り当てることができる
function void MPI_Aint int MPI_Info MPI_Comm void MPI_Win
MPI_Win_create *base size disp_unit info comm ----- *win
MPI_Win_allocate ----- size disp_unit info comm *baseptr *win
MPI_Win_create_dynamic ----- ----- ----- ----- comm ----- *win
MPI_Free ----- ----- ----- ----- ----- ----- *win
102
送受信関数の引数
• 呼出し側(caller)プロセスを origin、相手側(callee)を target と呼ぶ
• データ移動の始点を source、終点を destination と呼ぶ MPI_Put ⇒ origin=source, target=destination
MPI_Get ⇒ origin=destination, target=source
• target 側のデータは memory window 内側、origin 側のデータは外側
function origin compare result target
void int Datatype void void int Datatype int Aint int Datatype Op Win Request
MPI_Put *addr count type ----- ----- ----- ----- rank disp count type ----- win -----
MPI_Get *addr count type ----- ----- ----- ----- rank disp count type ----- win -----
MPI_Accumulate *addr count type ----- ----- ----- ----- rank disp count type op win -----
MPI_Get_accumulate *addr count type ----- *addr count type rank disp count type op win -----
MPI_Fetch_and_op *addr ----- ----- ----- *addr ----- type rank disp ----- ----- op win -----
MPI_Compare_and_swap *addr ----- ----- *addr *addr ----- type rank disp ----- ----- ----- win -----
MPI_Rput *addr count type ----- ----- ----- ----- rank disp count type ----- win *request
MPI_Rget *addr count type ----- ----- ----- ----- rank disp count type ----- win *request
MPI_Raccumulate *addr count type ----- ----- ----- ----- rank disp count type op win *request
MPI_Rget_accumulate *addr count type ----- *addr count type rank disp count type op win *request
c.f. abbreviations: MPI_Datatype => Datatype, MPI_Aint => Aint, MPI_Op => Op, MPI_Win => Win, MPI_Request => Request
また origin と compare の void は正確には const void
103
送受信関数の引数(続)
• 動作は非閉塞的であり、必ず同期が必要
• Put は origin の内容を target に代入、Get は target の内容を origin に代入
• Op は既定義の操作のみ(ユーザ定義は不可)、 origin と target の間で演算
• Accumurate は演算結果を target に格納 Op = MPI_REPLACE の場合は origin の内容を target に代入
• Get_accumulate は target の内容を result に保存後、演算結果を target に格納 Op = MPI_REPLACE の場合は origin の内容を target に代入
Op = MPI_NO_OP の場合は origin と target の内容は不変
• Fetch_and_op は target は Get_accumulate でデータが1要素の場合に特化
• Compare_and_swap も1要素のみ対象、target の内容を result に保存し、
compare と target の内容が等しい場合のみ origin の内容を target に代入
• Rのついた関数は request を返し MPI_Test, MPI_Wait 等で local に同期できる (但し、passive target synchronization でしか使えない)
104
同期関数の引数 function int int MPI_Group int MPI_Win int
MPI_Win_fence ----- ----- ----- assert win -----
MPI_Win_start ----- ----- group assert win -----
MPI_Win_complete ----- ----- ----- ----- win -----
MPI_Win_post ----- ----- group assert win -----
MPI_Win_wait ----- ----- ----- ----- win -----
MPI_Win_test ----- ----- ----- ----- win “flag
MPI_Win_lock type rank ----- assert win -----
MPI_Win_unlock ----- rank ----- ----- win -----
MPI_Win_flush ----- rank ----- ----- win -----
MPI_Win_flush_local ----- rank ----- ----- win -----
MPI_Win_lock_all ----- ----- ----- assert win -----
MPI_Win_unlock_all ----- ----- ----- ----- win -----
MPI_Win_flush_all ----- ----- ----- ----- win -----
MPI_Win_flush_local_all ----- ----- ----- ----- win -----
MPI_Win_sync ----- ----- ----- ----- win -----
105
同期の方法
Active target: • origin と target の両側で同期関数を呼ぶ • origin は access epoch の間だけ通信できる • target は exposure epoch の間だけウィンドウを公開する • 以下の2種類の方法がある
a. MPI_Win_fence b. MPI_Win_start + MPI_Win_complete / MPI_Win_post + MPI_Win_wait
Passive target: • origin 側のみが同期関数を呼ぶ
• origin は access epoch の間だけ通信できる • target は同期関数を呼ばないので epoch はない • 方法は下記1種類のみ a. MPI_Win_lock + MPI_Win_unlock,
(MPI_Win_lock_all + MPI_Win_unlock_all)
106
Active target (collective synchronization)
MPI_Win_fence を使う
MPI_Win_fence は collective call である
MPI_Win_fence と MPI_Win_fence の間が epoch になり、
後ろのMPI_Win_fence がその間の通信を完了する
通信関数を呼ぶと origin なので access epoch と解釈される
通信関数を呼ばない場合は exposure epoch と解釈される
コミュニケータ内の全プロセスが全ウィンドウにアクセス可能
通信相手の数が多く、しかも頻繁に変わる場合に使う
MPI_Win_fence
MPI_Put
MPI_Get
MPI_Win_fence
MPI_Win_fence
MPI_Win_fence
origin process target process
buffer
window
access epoch exposure epoch buffer
107
Active target (restricted synchronization)
origin 側に MPI_Win_start/MPI_Win_complete 、
target 側に MPI_Win_post/MPI_Win_wait を使う
ウィンドウアクセスの相手は MPI_Win_start で group を指定
ウィンドウを公開する相手はMPI_Win_post で group を指定
実装によって target が post するまで通信は始まらず
origin が complete した後に wait が復帰する (strong synchronization)
バッファリングする実装ではこの制約は無い (weak synchronization)
通信相手の数が少なく、しかも固定的な場合に使う
MPI_Win_start( group, … )
MPI_Put
MPI_Get
MPI_Win_complete
MPI_Win_post( group, … )
MPI_Win_wait
origin process target process
buffer
window
access epoch exposure epoch buffer
*MPI_Win_test はMPI_Win_wait の非閉塞版
108
Passive target (lock synchronization)
origin 側に MPI_Win_lock/MPI_Win_unlock 、
または MPI_Win_lock_all/MPI_Win_unlock_all を使う
ウィンドウアクセスの相手は lock/unlock では rank のみ
lock_all/unlock_all では win の全プロセス
lock_all/unlock_all は collective call ではない
target のウィンドウを複数のプロセスで同時に使う場合
type に MPI_LOCK_SHARED を1つのプロセスが排他的(atomic)
に使用する場合 MPI_LOCK_EXCLUSIVE を指定する
MPI_Win_lock( type, rank, … )
MPI_Put
MPI_Get
MPI_Win_unlock( rank, … )
origin process target process
buffer
window
access epoch buffer
• 一般に flush は待ち状態の送受信を
強制的に完了させる
• MPI_Win_flush は rank に対する送受信、
MPI_Win_fllush_all は win 内の全送受信
を origin, target の両側で完了させる
• MPI_Win_flush_local, MPI_Win_flush_local_all
は origin 側のみ完了させる
• MPI_Win_sync はウィンドウを整合化し
epoch を仕切りなおす
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
109
マルチスレッド時のMPI関数実行
MPIではマルチスレッド時の実行に関して
低い順に以下の4つのレベルが設定されている
実行環境により設定可能なレベルは異なる
• MPI_THREAD_SINGLE
シングルスレッド時のみ実行可能
• MPI_THREAD_FUNNELED
マルチスレッド時はマスタースレッドのみ実行可能
• MPI_THREAD_SERIALIZED
マルチスレッド時は同時に一つのスレッドのみ実行可能*
• MPI_THREAD_MULTIPLE
マルチスレッド時も制約なしにどのスレッドも実行可能
*スレッド間の排他制御はユーザの責任
110
レベル指定付き初期化
int MPI_Init_thread( int *argc, char ***argv, int required, int *provided )
int MPI_Query_thread( int *provided ) int MPI_Is_thread_main( int *flag )
int provided; MPI_Init_thread( argc, argv, MPI_THREAD_MULTIPLE, &provided ); if( provided!=MPI_THREAD_MULTIPLE ) printf( “MPI_THREAD_MULTIPLE is not supported!¥n” );
int provided, flag; MPI_Query_thread( &provided ); if( provided!=MPI_THREAD_MULTIPLE ) printf( “MPI_THREAD_MULTIPLE is not supported!¥n” ); MPI_Is_thread_main( &flag ); if( flag ) printf( “I am master thread!¥n” );
注:MPI_Init 同様、 argc, argv は省略可能
requiredはプロセス毎に異なる値でも可
Function prototype
Example (thread01.c)
Function prototype
Example (thread02.c)
問い合わせ関数
111
スレッド共有記憶の設定
112
MPI_Comm oldcomm, newcomm; MPI_Info info = MPI_INFO_NULL; int type = MPI_COMM_TYPE_SHARED; int key = ; MPI_Comm_split_type( oldcomm, type, key, info, newcomm );
Example (-----.c)
int MPI_Win_allocate_shared( MPI_Aint size, int disp_unit, MPI_Info info, MPI_Comm comm, void *baseptr, MPI_Win win )
Function prototype
• comm のプロセスが共有する size バイトのメモリウィンドウ win
を割当てそのポインタを baseptr に返す
int MPI_Win_shared_query( MPI_Win win, int rank, MPI_Aint size, int disp_unit, void *baseptr )
Function prototype
• MPI_Win_allocate_shared で割当てたメモリウィンドウの情報を返す
スレッド共有記憶の設定
113
MPI_Comm oldcomm, newcomm; MPI_Info info = MPI_INFO_NULL; int type = MPI_COMM_TYPE_SHARED; int key = ; MPI_Comm_split_type( oldcomm, type, key, info, newcomm );
Example (-----.c)
int MPI_Comm_split_type( MPI_Comm comm, int split_type, int key, MPI_Info info, MPI_Comm newcomm )
Function prototype
• 共有記憶領域毎に分離したコミュニケータを生成
MPI_Comm_create_group ( comm, group, tag, *newcomm )
・comm はイントラのみ ・group 内で collective call ・tag はマルチスレッド実行の時に使う
MPI_Mprobe/MPI_Mrecv /MPI_Improbe/MPI_Imrecv
複数のスレッドがMPI_Probeを実行した時、タイミングによって後続の MPI_Recvがデータを取り違える可能性がある
MPI_THREAD_MULTIPLE環境では thread safe な MPI_Mprobe と MPI_Mrecv (Matching Probe/Recv)を使う
MPI_Improbe と MPI_Imrecv はその非閉塞バージョン
ProbeとRecvは閉塞と非閉塞を組み合わせてもよい
int MPI_Mprobe( int source, int tag, MPI_Comm comm, MPI_Message *message, MPI_Status *status ) int MPI_Mrecv( void *buf, int count, MPI_Datatype datatype, MPI_Message *message, MPI_Status *status ) int MPI_Improbe( int source, int tag, MPI_Comm comm, int *flag, MPI_Message *message, MPI_Status *status ) int MPI_Imrecv( void *buf, int count, MPI_Datatype datatype, MPI_Message *message, MPI_Request *request )
注:MPI_Improbeの引数にはrequestが無い(Wait/Test不要)、代わりにflagがある(受信可能なデータの有無)。
またMPI_Imrecvの引数にはstatusが無い(Wait/Testで取得)
Function prototype
114
int *rbuf; int count; MPI_Status status; MPI_Message message; if( master ) { MPI_Mprobe( MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &message, &status ); MPI_Get_count( &status, MPI_INT, &count ); MPI_Alloc_mem( count*sizdof(int), MPI_INFO_NULL, rbuf ); MPI_Mrecv( rbuf, count, MPI_INT, &message, MPI_STATUS_IGNORE ); }
int *rbuf; int count, flag; MPI_Status status; MPI_Message message; MPI_Request request; if( master ) { MPI_Improbe( MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &flag, &message, &status ); if( !flag ) continue; MPI_Get_count( &status, MPI_INT, &count ); MPI_Alloc_mem( count*sizdof(int), MPI_INFO_NULL, rbuf ); MPI_Imrecv( rbuf, count, MPI_INT, &message, &request ); MPI_Wait( &request, MPI_STATUS_IGNORE ); }
Example (mprobe.c)
Example (improbe.c)
115
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
116
派生データ型とは
基本型を組み合わせてユーザが定義する構造を持ったデータ型
ギャップを挟んだデータや異なった型をメンバーにもつ構造体なども定義できる
さらに派生データ型を組み合わせた派生データ型も定義可能
注意点
関数名に一部整合的でないものが存在する(動詞Createが欠けている)
長さの指定が要素数の場合とバイト数の場合がある ギャップを詰めて圧縮したい場合は MPI_Pack で圧
縮し MPI_Unpack で復元する
117
派生データ型関数一覧
Definition
118
MPI_Type_contiguous
MPI_Type_vector
MPI_Type_create_indexed_block
MPI_Type_indexed
MPI_Type_create_struct
MPI_Type_create_subarray*
MPI_Type_create_darray*
MPI_Type_create_resized*
Elements Bytes
MPI_Type_create_hvector
MPI_Type_create_hindexed_block
MPI_Type_create_hindexed
management
MPI_Type_commit
MPI_Type_free
MPI_Type_dup*
MPI_Get_address*
* MPI_Type_create_subarray, MPI_Type_create_darray はファイルI/Oで説明する MPI_Type_create_resized, MPI_Type_dup, MPI_Get_address の説明は省略
派生データ型関数一覧(続)
119
Information
MPI_Type_size
MPI_Type_get_extent*
MPI_Type_get_true_extent*
MPI_Type_get_elements*
MPI_Type_get_envelope*
MPI_Type_get_contents*
int/Aint
MPI_Type_size_x*
MPI_Type_get_extent_x*
MPI_Type_get_true_extent_x*
MPI_Type_get_elements_x*
MPI_Count
MPI_Unpack
MPI_Unpack_external*
MPI_Pack
MPI_Pack_size
MPI_Pack_external*
MPI_Pack_external_size*
Deposit Withdrawal
Pack/Unpack
* MPI_Type_size, MPI_Pack , MPI_Unpack , MPI_Pack_size 以外の説明は省略
MPI_Type_contiguous
int MPI_Type_contiguous( int count , MPI_Datatype oldtype , MPI_Datatype *newtype )
MPI_Datatype newtype;
MPI_Type_contigous(4, MPI_INT, &newtype);
Function prototype
Example (-----.c)
• oldtype のデータを count 個連接した型を定義する
120
newtype = int int int int
MPI_Type_vector/MPI_Type_create_hvector
int MPI_Type_vector(int count , int blocklength, int stride, MPI_Datatype oldtype, MPI_Datatype *newtype) int MPI_Type_create_hvector(int count , int blocklength, MPI_Aint stride, MPI_Datatype oldtype, MPI_Datatype *newtype)
MPI_Datatype newtype;
MPI_Type_vector(3, 2, 4, MPI_INT, &newtype);
Function prototype
Example (-----.c)
• 連続する blocklengh 個のデータを count 回繰り返す
• ブロック間の距離は stride で指定する
• stride の単位は vector は要素数、hvector はバイト数(h は heterogeneous の頭文字)
121
newtype = int int int int int int int int int int
blocklength
count
stride
(注:これはvectorの場合)
MPI_Type_create_indexed_block /MPI_Type_create_hindexed_block
int MPI_Type_create_indexed_block(int count , int blocklength, const int array_of_displacements[], MPI_Datatype oldtype, MPI_Datatype *newtype) int MPI_Type_create_hindexed_block(int count , int blocklength, const MPI_Aint array_of_displacements[], MPI_Datatype oldtype, MPI_Datatype *newtype)
MPI_Datatype newtype;
const int displacements = {4,3,0}; MPI_Type_indexed_block(3, 2, displacements, MPI_INT, &newtype);
Function prototype
Example (-----.c)
• MPI_Type_vector で stride の指定を可変にしたもの
• array_of_displacements の単位は indexed_block は要素数、hindexed_block はバイト数
122
newtype = int int int int int int int int int
blocklength
count
displacements[0] displacements[1]
(注:これはindexed_blockの場合)
MPI_Type_indexed/MPI_Type_create_hindexed
int MPI_Type_indexed(int count , const int array_of_blocklengths[], const int array_of_displacements[], MPI_Datatype oldtype, MPI_Datatype *newtype) int MPI_Type_create_hindexed(int count , const int array_of_blocklengths[], const MPI_Aint array_of_displacements[], MPI_Datatype oldtype, MPI_Datatype *newtype)
MPI_Datatype newtype;
const int blocklengths = {2,1,3}; const int displacements = {4,3,0};
MPI_Type_indexed(3, blocklengths, displacements, MPI_INT, &newtype);
Function prototype
Example (-----.c)
• MPI_Type_vector で blocklengh, stride の指定を可変にしたもの
• array_of_displacements の単位は indexed は要素数、hindexed はバイト数
123
newtype = int int int int int int int int int int
blocklengths[0]
count
displacements[0]
blocklengths[1] blocklengths[2]
displacements[1]
(注:これはindexedの場合)
MPI_Type_create_struct
int MPI_Type_create_struct(int count , const int array_of_blocklengths[], const MPI_Aint array_of_displacements[], MPI_Datatype array_of_types[], MPI_Datatype *newtype)
MPI_Datatype newtype;
const int blocklengths = {2,1,3}; const int displacements = {16,24,0};
MPI_Datatype types = {MPI_INT, MPI_FLOAT, MPI_CHAR};
MPI_Type_indexed(3, blocklengths, displacements, types, &newtype);
Function prototype
Example (-----.c)
• MPI_Type_vector で blocklengh, stride, oldtype の指定を可変にしたもの
• array_of_displacements の単位はバイト数
124
newtype =
blocklengths[0]
count
displacements[0]
blocklengths[1] blocklengths[2]
displacements[1]
int int float char char char
MPI_Type_commit
int MPI_Type_commit(MPI_Datatype *datatype)
MPI_Datatype newtype;
MPI_Type_commit(&newtype);
Function prototype
Example (-----.c)
• 派生データ型を通信に使えるよう有効化する
125
MPI_Type_free
int MPI_Type_free(MPI_Datatype *datatype)
MPI_Datatype newtype;
MPI_Type_free(&newtype);
Function prototype
Example (-----.c)
• 派生データ型を解放する(deallocateする)
MPI_Pack
int MPI_Pack(const void *inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outsize, int *position, MPI_Comm comm)
Function prototype
• inbuf から始まる incount 個のdatatype 型データを outbuf から始まる長さ outsize の
バッファに push した後にカレントポインタ position を返す
126
float in0[2] = {0.1, 0.2};
int in1[3] = {11, 12, 13}; int position = 0;
char out[1000];
MPI_Comm comm = MPI_COMM_WORLD; MPI_Pack(in0, 2, MPI_FLOAT, out, 1000, &position, comm);
MPI_Pack(in1, 3, MPI_INT, out, 1000, &position, comm);
MPI_Send(out, position, MPI_PACKED, 1, 9, MPI_COMM_WORLD);
Example (-----.c)
outbuf = float int int float int
1000 bytes
incount
float float inbuf =
incount
int inbuf = int int
position position position
Function prototype
127
MPI_Unpack
• inbuf から始まる長さ insize のバッファのカレントポインタ position から、
outcount 個のdatatype 型データを pop して outbuf に格納する
int MPI_Unpack(const void *inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm)
float out0[2];
int out1[3]; int position = 0;
char in[1000];
MPI_Comm comm = MPI_COMM_WORLD; MPI_Recv(in, 1000, MPI_PACKED, 0, 9, comm, MPI_STATUS_IGNORE);
MPI_Pack(in, 1000, &position, out0, 2, MPI_FLOAT, comm);
MPI_Pack(in, 1000, &position, out1, 3, MPI_INT, comm);
Example (-----.c) • MPI_recv の recvsize が position でないことに注意
outcount
float float outbuf =
outcount
int outbuf = int int
inbuf = float int int float int
1000 bytes
position position position
MPI_Type_size
int MPI_Type_size(MPI_Datatype datatype, int *size)
MPI_Datatype mytype;
int size; MPI_Type_size(mytype, &size);
Function prototype
Example (-----.c)
• datatype 型の大きさをバイト数で size に返す
128
MPI_Pack_size
int MPI_Pack_size(int incount, MPI_Datatype datatype, MPI_Comm comm, int *size)
MPI_Datatype mytype;
int size; MPI_Pack_size(10, mytype, MPI_COMM_WORLD, &size);
Function prototype
Example (-----.c)
• incount 個の datatype 型データを pack するために必要なバイト数を size に返す
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
129
#define BUFSIZE 100 int buf[BUFSIZE]; char fname[128]; sprintf( fname, “myfile%.4d”, rank ); // myfile0000 fp = fopen( fname, “w” ); fwrite( buf, sizeof(int), BUFSIZE, fp ); fclose( fp );
書式なし分離ファイルの使用法
MPI_I/Oの特徴
書式なしファイル(unformatted file)のみ
主に共通ファイル(common file)が対象
分離ファイル(separate files)は作成のみ対応
a) POSIX
b) MPI
130
MPI_COMM_SELF
File I/O
MPI_File_open,
MPI_File_close,
MPI_File_delete,
MPI_File_preallocate,
MPI_File_set_view,
MPI_File_get_view,
MPI_File_set_size,
MPI_File_get_size,
MPI_File_set_info,
MPI_File_get_into,
MPI_File_set_atomicity,
MPI_File_get_atomicity,
MPI_File_seek,
MPI_File_seek_shared,
MPI_File_get_position,
MPI_File_get_position_shared,
MPI_File_get_byte_offset,
MPI_File_get_type_extent,
MPI_Register_datarep,
MPI_File_get_group,
MPI_File_get_amode,
MPI_File_sync,
131
MPI_File_read MPI_File_iread
MPI_File_read_at MPI_File_iread_at
MPI_File_read_shared MPI_File_iread_shared
MPI_File_read_all MPI_File_read_all_begin/end
MPI_File_read_at_all MPI_File_read_at_all_begin/end
MPI_File_read_ordered MPI_File_read_ordered_begin/end
MPI_File_write MPI_File_iwrite
MPI_File_write_at MPI_File_iwrite_at
MPI_File_write_shared MPI_File_iwrite_shared
MPI_File_write_all MPI_File_write_all_begin/end
MPI_File_write_at_all MPI_File_write_at_all_begin/end
MPI_File_write_ordered MPI_File_write_ordered_begin/end
Blocking
Read
Nonblocking
Write
individual-file-pointer
explicit_offset
shared-file-pointer
seek relatively with pointers
access directly without pointer
seek relatively with a pointer
collective I/O function
split collective I/O function
Independent I/O function
132
MPI_Type_create_subarray
int MPI_Type_create_subarray(int ndims , const int array_of_sizes[], const int array_of_subsizes[], const int array_of_starts[], int order, MPI_Datatype oldtype, MPI_Datatype *newtype)
MPI_Datatype newtype;
const int sizes = {100, 100}; const int subsizes = {25, 25};
const int starts = {20, 40};
MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_INT, &newtype);
Function prototype
Example (-----.c)
• ファイルI/Oでfiletypeに指定し、配列全体の一部だけ読み書きするために用いる
133
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
134
グループ・コミュニケータ・トポロジーの関係
135
イントラ・コミュニケータ
グループ
ranks = {0, 1, 2, …, n-1}
コンテキスト アトリビュート
・トポロジーは1つのイントラ・コミュニケータと 仮想トポロジーから構成される ・仮想トポロジーはアトリビュートの一種で ノード間の結合や重みなどの高次情報
・コミュニケータには2種類ある ・イントラ・コミュニケータはグループを1つ持つ ・インター・コミュニケータはグループを2つ持つ ・グループはランク番号の単純集合 ・コンテキストは通信範囲を限定する機能を持つ ・アトリビュートはユーザによって与えられる情報
インター・コミュニケータ
グループ
ranks = {0, 1, 2, …, n-1}
グループ
ranks = {0, 1, 2, …, m-1}
コンテキスト アトリビュート
仮想トポロジー
トポロジー
イントラ・コミュニケータ
グループ
ranks = {0, 1, 2, …, n-1}
コンテキスト アトリビュート
グループの編集
136
MPI_Group_size( group, *size )
・group1 の ranks1[i] 番のプロセスが group2 では何番に対応するかを rank2[i] に返す ・対応するものがなければ M MPI_UNDEFINED を返す
・group1とgroup2を比較する, 完全に同じ場合は result に MPI_IDENT, 順序だけ異なる場合は MPI_SIMILAR, その他は MPI_UNEQUAL を返す
MPI_Group_translate _ranks( group1, n, ranks1[ ], group2, *ranks2[ ] )
MPI_Group_free( *group )
・groupの要素数を返す
MPI_Group_rank( group, *rank )
・自プロセスのrank番号を返す、存在しない場合は MPI_UNDEFINED を返す
MPI_Group_compare( group1, group2, *result )
・group に MPI_GROUP_NULLを代入し無効にする
* MPI_Comm_group と MPI_Group_xxx は全て non-collective
・コミュニケータ comm からグループ group を返す ・comm がインター・コミュニケータの場合は local group の group を返す
MPI_Comm_group( comm, *group )
グループの編集(続)
137
MPI_Group_union( group1, group2, *newgroup )
MPI_Group_intersection( group1, group2, *newgroup )
MPI_Group_difference( group1, group2, *newgroup )
group1 = {0, 1, 2, 3, 4, 5},
group2 = {2, 4, 6, 8,10}
*newgroup = {0, 1, 2, 3, 4, 5, 6, 8,10}
*newgroup = {2, 4}
*newgroup = {0, 1, 3, 5}
group 1 group 2
*newgroup
MPI_Group_union
group 1 group 2
*newgroup
MPI_Group_intersection
group 1 group 2
*newgroup
MPI_Group_difference
・ユーザが新規にグループを定義することはできない ・既存のコミュニケータから抽出したグループを編集する
* 空集合の場合は MPI_GROUP_EMPTY が返される
グループの編集(続)
138
MPI_Group_incl( group, 5, ranks, *newgroup )
MPI_Group_excl( group, 5, ranks, *newgroup )
group = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12}, ranks = {1, 3, 6, 9,12}
*newgroup = {1, 3, 6, 9,12}
*newgroup = {0, 2, 4, 5, 7, 8,10,11}
・ranges は (first, last, stride) の組の繰り返し 上の例では(0,7,2)と(8,12,3)の2組
・各組は first から last まで stride おきの番号を指定する ・指定された番号が group の要素に無い時はエラーになる ・range_incl は group から指定された番号を抜き出す ・range_excl は group から指定された番号を削除する ・range_incl と range_excl の和集合は group になる
group = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12}, ranges = {0, 7, 2, 8,12, 3}
MPI_Group_range_incl( group, 2, ranges, *newgroup )
MPI_Group_range_excl( group, 2, ranges, *newgroup )
*newgroup = {0, 2, 4, 6, 8,11}
*newgroup = {1, 3, 5, 7, 9,10,12}
・ranks の要素が group の要素に無い時はエラーになる ・incl は group から ranks で指定したランクを抜き出す ・excl は group から ranks で指定したランクを削除する ・incl と excl の和集合は group になる
(0,7,2) (8,12,3)
コミュニケータの編集
139
MPI_Comm_size( comm, *size )
・comm1 と comm2 を比較し, 完全に一致する場合は MPI_IDENT, ランク番号の順序だけ異なる場合は MPI_SIMILAR, context だけ異なる場合は MPI_CONGRUENT, その他は MPI_UNEQUAL を result に返す ・comm 内で non-collective call
・comm の要素数を返す ・comm がインター・コミュニケータの場合は local group の要素数を返す ・comm 内で non-collective call
MPI_Comm_rank( comm, *rank )
・自プロセスの rank 番号を返す ・comm がインター・コミュニケータの場合は local group 内の rank 番号を返す ・comm 内で non-collective call
MPI_Comm_compare( comm1, comm2, *result )
コミュニケータの編集(続)
140
・MPI_Comm_dup の非閉塞版 ・comm 内で collective call
MPI_Comm_free( *comm )
・comm の複製を *newcomm に返す ・comm 内で collective call
・comm を複製し info を上書きして *newcomm に返す ・comm 内で collective call
・comm に MPI_COMM_NULLを代入し無効にする ・MPI_COMM_WORLD には適用しない(できない) ・comm 内で collective call
MPI_Comm_dup ( comm, *newcomm )
MPI_Comm_dup_with_info (comm, info, *newcomm )
MPI_Comm_idup ( comm , *newcomm , *request )
コミュニケータの編集(続)
141
MPI_Comm_create( comm, group, *newcomm )
・comm の部分集合 group のコミュニケータを作成し *newcomm に返す ・group に属さないプロセスには *newcomm に MPI_COMM_NULL を返す ・comm はイントラ・インターどちらも可、*newcomm は comm と同種 ・comm の attribute は引き継がない ・comm 内で collective call
インター・コミュニケータ
イントラ・コミュニケータ インター・コミュニケータ で group が1つの場合と同じ
MPI_Comm_create_group ( comm, group, tag, *newcomm )
・マルチスレッドの項で説明
MPI_Comm_group
MPI_Gtoup_incl etc. MPI_Comm_create
group group
sub-group sub-group *newcomm
sub-group sub-group
comm
group group
コミュニケータの編集(続)
142
・color 毎に comm の部分コミュニケータを同名で作成し *newcomm に返す ・ランク番号は key の値が小さいものから 0, 1, 2, … の順に付けられる ・color=MPI_UNDEFINED と指定すると *newcomm には MPI_COMM_NULL を返す ・comm 内で collective call
MPI_Comm_split ( comm, color, key, *newcomm )
MPI_Comm_split_type ( comm, color, key, *newcomm )
・マルチスレッドの項で説明
MPI_Comm_split
+
(color,key)
comm
group
(red,1) (red,2)
(blue,3) (blue,1)
group
(red,0) (red,2)
(blue,1)
*newcomm
group
rank=0 rank=1
group
rank=0 rank=1
*newcomm
group
rank=1 rank=0
group
rank=0
インター・コミュニケータの場合
イントラ・コミュニケータの場合 インター・コミュニケータ で group が1つに同じ
インター・コミュニケータの編集
143
MPI_Comm_remote_group ( comm, *group )
・comm がインター・コミュニケータであれば flag に true を返す ・comm 内で non-collective call
MPI_Comm_remote_size ( comm, *size )
・remote group の size を返す ・comm 内で non-collective call
MPI_Comm_test_inter ( comm, *flag )
・remote group を返す ・comm 内で non-collective call
MPI_Intercomm_merge ( intercomm, high, *newintracomm )
・intercomm を イントラ・コミュニケータに変換して *newintracomm に返す ・high=false の group から昇順にランク番号を付ける ・intercomm 内で collective call
144
インター・コミュニケータの編集(続)
MPI_Intercomm_create ( local_comm, local_leader, peer_comm, remote_leader, tag, *newintercomm )
・local_comm と remote_leader が属するコミュニケータから インター・コミュニケータを生成し *newintercomm に返す ・両方のコミュニケータに属するプロセスが存在してはならない (disjoint communicators) ・peer_comm は local_comm と remote_comm を包含するコミュニケータ ・local_leader は local_comm 内のランク番号 ・remote_leader は peer_comm 内のランク番号 ・local_comm と peer_comm 内で collective call
group
local_comm
group
remote_comm
peer_comm peer_comm
group group
*newintercomm MPI_Intercomm_create
MPI_Intercomm_merge peer_comm
group
*newintracomm
peer_comm
group group
intercomm
インター・コミュニケータの作成例
MPI_COMM_WORLDからイントラ・コミュニケータを2つ(Fluid,Solid)作る 各々のコミュニケータ内でランク番号が設定される (F0, F1, F2, S0, S1, S2, S3) FluidとSolidからインター・コミュニケータAircraftを作る
MPI_COMM_WORLD
P3
Fluid
P2,F2
P1,F1
P0,F0
Solid
P6,S2
P5,S1 P7,S3
P4,S0
Aircraft
145
int main( int argc, char *argv[] ) { int rank, size; MPI_Init( &argc, &argv ); MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Group MPI_GROUP_WORLD, fgroup, sgroup; MPI_Comm_group( MPI_COMM_WORLD, &MPI_GROUP_WORLD ); int franks[3] = { 0, 1, 2 }; int sranks[4] = { 4, 5, 6, 7 }; MPI_Group_incl( MPI_GROUP_WORLD, 3, franks, &fgroup ); MPI_Group_incl( MPI_GROUP_WORLD, 4, sranks, &sgroup ); MPI_Comm Fluid, Solid, Aircraft; MPI_Comm_create( MPI_COMM_WORLD, fgroup, &Fluid ); MPI_Comm_create( MPI_COMM_WORLD, sgroup, &Solid ); if( fcomm!=MPI_COMM_NULL ) { const int fleader = 0; const int sleader = 4; MPI_Intercomm_create( Fluid, fleader, MPI_COMM_WORLD, sleader, 0, &Aircraft ); } if( scomm!=MPI_COMM_NULL ) { const int sleader = 0; const int fleader = 0; MPI_Intercomm_create( Solid, sleader, MPI_COMM_WORLD, fleader, 0, &Aircraft ); } MPI_Finalize(); return 0; }
WORLDから部分集合 のグループを2つ作る
Example (intercomm.c)
146
それぞれのグループから コミュニケータを生成する
2つのコミュニケータから インター・コミュニケータを生成する
インター通信(Inter-communication)
2つのgroup間で通信する
集団通信では MPI_IN_PLACE は使えない
要素通信では dest, source に remote の rank を指定
片側通信はインター通信できない
147
MPI_Bcast
Fluidのプロセス1からSolidにBcastする場合
comm=Aircraftに設定する
rootを以下のよう設定する F0: root = MPI_PROC_NULL
F1: root = MPI_ROOT
F2: root = MPI_PROC_NULL
S0: root = 1
S1: root = 1
S2: root = 1
S3: root = 1
MPI_COMM_WORLD
P3
Fluid
P2,F2
P1,F1
P0,F0
Solid
P6,S2
P5,S1 P7,S3
P4,S0
Aircraft
148
MPI_Scatter/MPI_Scatterv
comm=Aircraftに設定する
rootを以下のよう設定する
Fluidのプロセス1の値が分割されSolidの全プロセスに格納
通信はBcastと同方向
F0: root = MPI_PROC_NULL
F1: root = MPI_ROOT
F2: root = MPI_PROC_NULL
S0: root = 1
S1: root = 1
S2: root = 1
S3: root = 1
MPI_COMM_WORLD
P3
Fluid
P2,F2
P1,F1
P0,F0
Solid
P6,S2
P5,S1 P7,S3
P4,S0
Aircraft
149
MPI_Gather/MPI_Gatherv/MPI_Reduce
MPI_COMM_WORLD
P3
Fluid
P2,F2
P1,F1
P0,F0
Solid
P6,S2
P5,S1 P7,S3
P4,S0
Aircraft
comm=Aircraftに設定する
rootを以下のよう設定する
Solid内の全プロセスの値が集められFluidのプロセス1に格納
通信はBcastと逆方向
F0: root = MPI_PROC_NULL
F1: root = MPI_ROOT
F2: root = MPI_PROC_NULL
S0: root = 1
S1: root = 1
S2: root = 1
S3: root = 1
150
MPI_Allgather/MPI_Allgatherv/MPI_Allreduce comm=Aircraftに設定する
rootは設定不要 Solid内の全プロセスの値が集められFluidの各プロセスに格納
Fluid内の全プロセスの値が集められSolidの各プロセスに格納 通信は双方向のAllgather!
sendcount=0にすると単方向のAllgatherになる
MPI_COMM_WORLD
P3
Fluid
P2,F2
P1,F1
P0,F0
Solid
P6,S2
P5,S1 P7,S3
P4,S0
Aircraft
151
MPI_Alltoall/MPI_Alltoallv/MPI_Alltoallw comm=Aircraftに設定する
rootは設定不要 Solid内の全プロセスの値が分割されFluidの各プロセスに連結格納
Fluid内の全プロセスの値が分割されSolidの各プロセスに連結格納 通信は双方向のAlltoall!
sendcount=0にすると単方向のAlltoallになる
MPI_COMM_WORLD
P3
Fluid
P2,F2
P1,F1
P0,F0
Solid
P6,S2
P5,S1 P7,S3
P4,S0
Aircraft
152
MPI_Reduce_scatter_block/MPI_Reduce_scatter comm=Aircraftに設定する
rootは設定不要 Solid内の全プロセスの値が総和されFluidの各プロセスに分割格納
Fluid内の全プロセスの値が総和されSolidの各プロセスに分割格納 通信は双方向のReduce_scatter!
MPI_Barrier comm=Aircraftに設定する
Solid内の全プロセスがコールまでFluidのプロセスは待機
Fluid内の全プロセスがコールまでSolidのプロセスは待機
非閉塞通信の動作・使用方法も
完了手順を除いて閉塞通信と同じ
153
Topology
MPI_Neighbor_allgather, MPI_Ineighbor_allgather,
MPI_Neighbor_allgatherv, MPI_Ineighbor_allgatherv,
MPI_Neighbor_alltoall, MPI_Ineighbor_alltoall,
MPI_Neighbor_alltoallv, MPI_Ineighbor_alltoallv,
MPI_Neighbor_alltoallw, MPI_Ineighbor_alltoallw,
MPI_Graph_create,
MPI_Topo_test,
MPI_Graphdims_get,
MPI_Graph_get,
MPI_Graph_neighbors_count,
MPI_Graph_neighbors,
MPI_Graph_map,
MPI_Cart_create,
MPI_Dims_create,
MPI_Cartdim_get,
MPI_Cart_get,
MPI_Cart_rank,
MPI_Cart_coords,
MPI_Cart_shift,
MPI_Cart_sub,
MPI_Cart_map,
MPI_Dist_graph_create_adjacent,
MPI_Dist_graph_create,
MPI_Dist_graph_neighbors_count,
MPI_Dist_graph_neighbors,
154
Inter-communication はできない
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
155
プロセス生成とは
MPIプログラム実行中、動的にプロセスを生成可能 Unixのforkに似た機能 生成したプロセス上で任意のプログラムを実行可能 呼出し側プロセスはインター・コミュニケータの local に、
生成されたプロセスは remote に置かれる コミュニケータを共有しないプロセス間にも
socketのようなポートを設定することで通信可能
注意点
呼出し側は必ずイントラ・コミュニケータ 生成しても MPI_COMM_WORLD は不変
(インター・コミュニケータに包含される)
ポートの動作は実装や環境に依存する(Ubuntuは不可?)
156
157
プロセス生成の例
最初 MPI_COMM_WORLD は4つのプロセスを持つとする MPI_Comm_spawn を使い comm 以外の引数を以下のように仮定する
・ command=“worker” プログラム名はworker ・ argv[]=MPI_ARGV_NULL オプションは無し ・ maxprocs=3 3つのプロセスを生成する ・ info=MPI_INFO_NULL infoは無し ・ root=0 rootはランク0番とする ・ errcode[]=MPI_ERROCODES_IGNORE エラーコードは無視する
・ comm の値が MPI_COMM_WORLD か MPI_COMM_SELF かによって生成されるプロセスの構成が異なる
MPI_Comm_spawn ( command , *argv[], maxprocs, info , root, comm, *intercomm, errcode[] )
・maxprocs 個のプロセスを生成し argv をオプションとして command を実行する ・生成したプロセスを remote とするインター・コミュニケータを inter_comm に返す
MPI_Comm_get_parent( *parent )
・親プロセス parent に返す
MPI_Comm_spawn_multiple(…)
・MPI_Comm_spawn で command が複数の場合、command 毎に argv 等を設定する
• MPI_Comm_join には Berkeley socket interface が 必要なので説明は省略
#include <stdio.h> #include “mpi.h” int main( int argc, char *argv[] ) { int rank, size; MPI_Init( &argc, &argv ); MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm intercomm; MPI_Comm comm = MPI_COMM_SELF; // MPI_Comm comm = MPI_COMM_WORLD; int maxprocs = 3, root = 0; MPI_Comm_spawn( “worker”, MPI_ARGV_NULL, maxprocs, MPI_INFO_NULL, root, comm, &intercomm, MPI_ERROCODES_IGNORE ); int wrank, wsize, rsize; MPI_Comm_size( intercomm, &wsize ); MPI_Comm_rank( intercomm, &wrank ); MPI_Comm_remote_size( intercomm, &rsize ); MPI_Finalize(); return 0; }
Example (commspawn.c)
158
インター・コミュニケータのサイズとランクを取得
プロセスを3つ生成し worker を実行
Remote group のサイズを取得
#include <stdio.h> #include “mpi.h” int main( int argc, char *argv[] ) { MPI_Comm parent; int rank, size, rsize; MPI_Init( &argc, &argv ); MPI_Comm_get_parent( &parent ); MPI_Comm_remote_size( parent, &rsize ); MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Finalize(); return 0; }
Example (worker.c)
159
親プロセスを取得
親プロセスのサイズを取得
160
comm == MPI_COMM_WORLD
local remote
inter
comm == MPI_COMM_SELF
inter inter
local
remote
local
remote
local local
remote remote
inter inter
・ local group はMPI_COMM_WORLD ・ remote group は生成した3つのプロセス ・ intercomm は1つ
最初のMPI_COMM_WORLDのプロセス
・ local group は各プロセス(i.e. MPI_COMM_SELF)
・ 各プロセスに3プロセスの remote group ・ intercomm は4つ
ポートを使ったプロセス間通信
MPI_Open_port,
MPI_Close_port,
MPI_Comm_accept,
MPI_Comm_connect,
MPI_Comm_disconnect,
MPI_Publish_name,
MPI_Unpublish_name,
MPI_Lookup_name,
161
MPI関数各論
1. 集団通信
2. 要素通信
3. 片側通信
4. マルチスレッド
5. 派生データ型
6. ファイルI/O
7. コミュニケータ・トポロジー
8. プロセス生成
9. その他
162
Environmental
MPI_Get_version,
MPI_Get_library_version,
MPI_Get_processor_name,
MPI_Alloc_mem,
MPI_Free_mem,
MPI_Wtime,
MPI_Wtick,
163 * MPI_Init_thread, MPI_Query_thread, MPI_Is_thread_main はマルチスレッドで説明する
バージョン、ノード名、メモリ割当て、時刻 MPIプログラムの初期化、終了、スレッド
MPI_Init,
MPI_Init_thread*,
MPI_Initialized,
MPI_Finalize,
MPI_Finalized,
MPI_Abort,
MPI_Query_thread*,
MPI_Is_thread_main*,
MPI_Get_version は整数、MPI_Get_library_version は文字列で返す MPI_Get_processor_name は Unix のhostname と同じ MPI_Alloc_mem, MPI_Free_mem は通常 malloc, free と同じだが最適化している場合もある MPI_Wtime は時刻、MPI_Wtick はサイクル数を返す
Info
164
コミュニケータ・ウィンドウ・ファイル・トポロジの関数引数に与える性能最適化のためのヒントを Info オブジェクトと呼ぶ
以下のような関数が利用可能
MPI_Info_create,
MPI_Info_dup,
MPI_Info_free,
MPI_Info_set,
MPI_Info_get,
MPI_Info_get_valuelen,
MPI_Info_get_nkeys,
MPI_Info_get_nthkey,
MPI_Info_delete,
MPI_Win_create,
MPI_Win_allocate,
MPI_Win_allocate_shared,
MPI_Win_create_dynamic,
MPI_Win_set_info,
MPI_Win_get_info,
MPI_File_open,
MPI_File_delete,
MPI_File_set_view,
MPI_File_set_info,
MPI_File_get_info,
Info オブジェクトを引数に持つ関数
MPI_Comm_spawn,
MPI_Comm_spawn_multiple,
MPI_Comm_dup_with_info,
MPI_Comm_accept,
MPI_Comm_connect,
MPI_Comm_open_port,
MPI_Comm_lookup_name,
MPI_Comm_publish_name,
MPI_Comm_unpbulish_name,
MPI_Comm_set_info,
MPI_Comm_get_info,
MPI_Dist_graph_create,
MPI_Dist_graph_create_adjucent,
Infoで利用可能な値は各ライブラリの ユーザマニュアル等に記載されている
Caching
165
コミュニケータ・ウィンドウ・派生データ型にAttributeと呼ばれる付加情報を持たせるために用いる
トポロジはコミュニケータにAttributeを加えることで実現されている
以下のような関数が利用可能
MPI_Comm_create_keyval,
MPI_Comm_free_keyval,
MPI_Comm_set_attr,
MPI_Comm_get_attr,
MPI_Comm_delete_attr,
MPI_Win_create_keyval,
MPI_Win_free_keyval,
MPI_Win_set_attr,
MPI_Win_get_attr,
MPI_Win_delete_attr,
MPI_Type_create_keyval,
MPI_Type_free_keyval,
MPI_Type_set_attr,
MPI_Type_get_attr,
MPI_Type_delete_attr,
typedef int MPI_Comm_copy_attr_function( MPI_Comm oldcomm, int keyval, void “val_in, void *val_out, int “flag );
typedef int MPI_Win_copy_attr_function( MPI_Win oldwin, int keyval, void “val_in, void *val_out, int “flag );
typedef int MPI_Type_copy_attr_function( MPI_Datatype oldtype, int keyval, void “val_in, void *val_out, int “flag );
typedef int MPI_Comm_delete_attr_function( MPI_Comm comm, int keyval, void *val, void *state );
typedef int MPI_Win_delete_attr_function( MPI_Win win, int keyval, void *val, void *state );
typedef int MPI_Type_delete_attr_function( MPI_Datatype type, int keyval, void *val, void *state );
create_keyval には以下のコールバック関数を指定する
Naming
166
コミュニケータ・ウィンドウ・派生データ型に文字列型の名前を持たせるために用いる
以下のような関数が利用可能
MPI_Comm_set_name,
MPI_Comm_get_name,
MPI_Win_set_name,
MPI_Win_get_name,
MPI_Type_set_name,
MPI_Type_get_name,
Error handling
167
コミュニケータ・ウィンドウ・ファイルにエラーハンドラを持たせるために用いる
エラークラスや文字列の注釈も設定できる 以下のような関数が利用可能
MPI_Comm_create_errhandler,
MPI_Comm_set_errhandler,
MPI_Comm_get_errhandler,
MPI_Comm_call_errhandler,
MPI_Win_create_errhandler,
MPI_Win_set_errhandler,
MPI_Win_get_errhandler,
MPI_Win_call_errhandler,
MPI_File_create_errhandler,
MPI_File_set_errhandler,
MPI_File_get_errhandler,
MPI_File_call_errhandler,
MPI_Error_class,
MPI_Error_string,
MPI_Errhandler_free, MPI_Add_error_class,
MPI_Add_error_code,
MPI_Add_error_string,
External interfaces
Generalized request:
MPI_Grequest_start,
MPI_Grequest_complete,
Status:
MPI_Status_set_elements,
MPI_Status_set_elements_x,
MPI_Status_set_cancelled,
168
ユーザ独自の非閉塞通信を定義するためのもの generalized request と status の振舞いを定義する 以下のような関数が利用可能
Tool support
Profiling interface:
MPI_Pcontrol,
Tool information interface:
MPI_T_pvar_get_num,
MPI_T_pvar_get_info,
MPI_T_pvar_get_index,
MPI_T_pvar_handle_alloc,
MPI_T_pvar_handle_free,
MPI_T_pvar_read,
MPI_T_pvar_write,
MPI_T_pvar_session_create,
MPI_T_pvar_session_free,
MPI_T_pvar_start,
MPI_T_pvar_stop,
MPI_T_pvar_reset,
MPI_T_pvar_readreset,
MPI_T_cvar_get_num,
MPI_T_cvar_get_info,
MPI_T_cvar_get_index,
MPI_T_cvar_handle_alloc,
MPI_T_cvar_handle_free,
MPI_T_cvar_read,
MPI_T_cvar_write,
MPI_T_enum_get_info,
MPI_T_enum_get_item,
MPI_T_Init_thraed,
MPI_T_finalize,
MPI_T_category_get_num,
MPI_T_category_get_info,
MPI_T_category_get_cvars,
MPI_T_category_get_pvars,
MPI_T_category_get_categories,
MPI_T_category_changed,
169
・ Tool information interface は MPI_T_init_thread と MPI_T_finalize の間で呼べる ・ MPI_Init の前や MPI_Finalize の後でも呼ぶことができる ・ pvar は performance variables、cvar は control variables の略 ・ C のみ定義されている(Fortranからは使えない)
並列性能評価
1. 並列性能について
2. 通信性能について
170
1. Strong scaling (Amdahl’s law)
2. Weak scaling (Gustafson’s law)
タスクを細分した場合の処理速度(speedup)
Gene M. Amdahl, Validity of the single processor approach to achieving large scale computing capabilities, AFIPS Conference Proceedings, (30), 483-485, 1967
John L. Gustafson, Reevaluating Amdahl’s law, Communication of the ACM, 31(5), 532-533, 1988
スケーリング性能
タスクを追加した場合の単位時間当たり処理量(throughput)
t=T0 t=T0/4 t=T0/9
x-FLOPS 4x-FLOPS 9x-FLOPS
171
• Amdahl’s law
逐次処理時間の内で並列部分(分割可能)を p 逐次部分を s とすると
並列処理時間は下図のようになる
𝑆 =𝑠 + 𝑝
𝑠 + 𝑝 𝑁 = 𝑁
𝛼 + 1
𝑁𝛼 + 1, 𝐸𝑎 =
𝑆
𝑁=
𝛼 + 1
𝑁𝛼 + 1 α =
𝑠
𝑝
逐次処理時間を並列処理時間で除した値を速度向上(Speedup)、
速度向上をプロセス数N で除した値を効率(Efficiency)と定義する
逐次処理
s p
並列処理
s p/N
十分な効率を得るには α は小さな値が必要
α 0.1 0.01 0.001 0.0001
N
10 0.550 0.918 0.991 0.999
100 0.100 0.505 0.909 0.990
1000 0.010 0.091 0.500 0.909
10000 0.001 0.010 0.090 0.500
Ea の例
172
• Gustafson’s law
並列処理時間の内で比例部分(並列可能)を p’ 定数部分を s’ とする、
もしこれを逐次処理した場合、処理時間は下図のようになる
𝐺 =𝑠 + 𝑁𝑝′
𝑠 + 𝑝′= 𝑁
1 + 𝛼′ 𝑁
1 + 𝛼′, 𝐸𝑔 =
𝐺
𝑁=1 + 𝛼′ 𝑁
1 + 𝛼′ 𝛼′ =
𝑠
𝑝′
逐次処理時間を並列処理時間で除した値を処理向上(throughput gain)
処理向上をプロセス数N で除した値を効率(Efficiency)と定義する
並列処理
s p’
比較的大きな 𝛼′ でも効率は良い値となる (c.f. 𝛼′ = 𝑁𝛼)
α’ 0.4 0.2 0.1 0.05
N
10 0.742 0.850 0.918 0.957
100 0.717 0.835 0.909 0.952
1000 0.714 0.833 0.909 0.952
10000 0.714 0.833 0.909 0.952
Eg の例
逐次処理
s Np’
173
・Strong scaling
•実測時間を用いた性能評価
プロセス数 n と m の2点で評価する (但しn>mとする)
𝑇𝑛 = 𝑠 + 𝑝 𝑛 , 𝑇𝑚 = 𝑠 + 𝑝 𝑚 , 𝑇𝑛はプロセス数 𝑛 の実測時間
𝑠 =𝑛𝑇𝑛 −𝑚𝑇𝑚𝑛 −𝑚
, 𝑝 =𝑛𝑚 𝑇𝑚 − 𝑇𝑛
𝑛 − 𝑚
Amdahl’s lawに従うと仮定すると
この式を解いて
従って
𝛼 =𝑠
𝑝=
𝑛𝑇𝑛 −𝑚𝑇𝑚𝑛𝑚 𝑇𝑚 − 𝑇𝑛
=𝑛 𝑚 − 𝑇𝑚 𝑇𝑛
𝑛 𝑇𝑚 𝑇𝑛 − 1
𝐸𝑎 =𝛼 + 1
𝑛𝛼 + 1, 𝑆 = 𝑛𝐸𝑎,
効率と速度向上は 𝛼 と 𝑛 を用いて以下の式で表される
一般に𝑚 が𝑛 に近い程良い値になる
𝑚 = 1 で評価できれば理想的
174
・Weak scaling
𝑇𝑛 = 𝑠 + 𝑝′, 𝑇𝑚 =𝑚
𝑛𝑠 + 𝑝′, 𝑇𝑛はプロセス数𝑛の実行時間
この式を解いて
プロセス数 n と m の2点で評価する (但しn>mとする)
𝑠 =𝑛 𝑇𝑛 − 𝑇𝑚𝑛 −𝑚
, 𝑝′ =𝑛𝑇𝑚 −𝑚𝑇𝑛𝑛 − 𝑚
従って
𝛼′ =𝑠
𝑝′=𝑛 𝑇𝑛 − 𝑇𝑚𝑛𝑇𝑚 −𝑚𝑇𝑛
=1 − 𝑇𝑚 𝑇𝑛
𝑇𝑚 𝑇𝑛 − 𝑚 𝑛
効率と処理向上は 𝛼 と 𝑛 を用いて以下の式で表される
𝐸𝑔 =1 + 𝛼′ 𝑛
1 + 𝛼′, 𝐺 = 𝑛𝐸𝑔,
Gustafson’s lawに従い、さらに 𝑠 はプロセス数に比例すると仮定 ∗
* Weak scalingの場合, 𝑠が通信, 𝑝′が計算タスクを含むと考えるのが合理的である
従って通信オーバーヘッド等プロセス数に依存するのは𝑠と見做すべきあるが、
解を得るには依存性に何らかの仮定が必要である 175
並列性能評価
1. 並列性能について
2. 通信性能について
176
Ping-pong 2つのプロセス間で交互に送受信
Full-bisection 全プロセスの半分が送信、残りが受信
Exchange 全プロセスが隣接プロセスと送受信
Intel MPI Benchmark(IMB) MPI関数の網羅的ベンチマークスイート
レイテンシ(Latency)と帯域幅(Band width)
ベンチマーク手法
177
データサイズ
時間
[KB]
[msec]
レイテンシ(usec)
帯域幅(GB/s)
典型的な通信性能
パケット長
レイテンシ: 通信準備等に必要な固定時間 帯 域 幅: 定常的な通信速度のこと
通信時間はデータサイズが小さい時はレイテンシに律速され、パケット長を超える辺りから帯域幅が効いてくる
具体的な問題への応用
1. 構造格子系(差分法)
2. 非構造格子系(有限要素法,有限体積法)
3. フーリエ変換
4. MPI Tips
178
• 領域分割法(Domain Decomposition)
2次元差分法 ヤコビ法によるポアソン方程式(∆𝜑 = 𝑓)の解法
計算格子全体を分割して各プロセスで分担
領域分割
全格子(18x10格子点) 6プロセスで領域分割 部分格子は6x5格子点
差分計算に必要となる内部境界点 (赤色)は隣接プロセスと通信 外部境界(青色)は境界条件を適用
境界のデータは隣接プロセスとの通信で取得
179
• ステンシル(Stencil)による通信パターンの違い
5点差分: 𝑤𝑖,𝑗 =1
4𝜑𝑖+1,𝑗 + 𝜑𝑖−1,𝑗 + 𝜑𝑖,𝑗+1 + 𝜑𝑖,𝑗−1 − ℎ2𝑓𝑖,𝑗
𝑤𝑖,𝑗 =1
8 𝜑𝑖+1,𝑗 + 𝜑𝑖−1,𝑗 + 𝜑𝑖,𝑗+1 + 𝜑𝑖,𝑗−1
+𝜑𝑖+1,𝑗+1 + 𝜑𝑖−1,𝑗+1 + 𝜑𝑖+1,𝑗−1 + 𝜑𝑖−1,𝑗−1 − 3ℎ2𝑓𝑖,𝑗
𝑖 + 1 𝑖 − 1 𝑖
𝑗 + 1
𝑗 − 1
𝑗
9点差分:
𝑖 + 1 𝑖 − 1 𝑖
𝑗 + 1
𝑗 − 1
𝑗
隣接する4つの
プロセスと通信
隣接する8つの
プロセスと通信
180
• 5点差分におけるMPI通信のポイント
カーテシアン・トポロジ(MPI_Cart_create)を使う 隣接プロセスの特定が容易なので間違う可能性が低い
プロセスの物理的配置(Affinity)をユーザ自身で最適化するのは困難だが、
(インターコネクトの物理的形状や通信バンド幅等の知識が必要)
ベンダー提供ライブラリでは最適化されている可能性がある
注:MPI_Dims_createの因数分解は(特に高次元で)あまり賢くない (例: 9=1x9 )
派生データ型を使う 派生データ型は一度定義しておけばPack/Unpackの必要がなく便利
ベンダーによっては特定ハードウェアに最適化している可能性がある
注:ベクトル化・マルチスレッド化したPack/Unpackの方が速い場合もある
複合通信(MPI_Send_recv)を使う 複数の送受信の順序をユーザ自身で最適化するのは困難
非閉塞通信も通信チャネル数や帯域幅が十分でないと無駄な待ちが発生する
多プロセス数のシフト通信(一斉に隣に送る)では特に効果的
注:閉塞通信なので通信の隠蔽はできない
181
( 1, 1, &a, &b )
Process b a
( 1, -1, &a, &b )
Process a b
MPI_Cart_shift が返すプロセス番号 a, b の位置関係
( 0, 1, &a, &b )
Process
a
b
( 0, -1, &a, &b )
Process
b
a
16 // define a cartesian topology 17 MPI_Comm cart2d; 18 int ndims = 2; 19 int reorder = true; 20 int dims[2] = { 0, 0 }; 21 int periods[2] = { false, false }; 22 int east, west, north, south; 23 MPI_Dims_create( size, ndims, dims ); 24 MPI_Cart_create( basecomm, ndims, dims, periods, reorder, &cart2d ); 25 MPI_Cart_shift( cart2d, 1, 1, &west, &east ); 26 MPI_Cart_shift( cart2d, 0, 1, &south, &north );
jacobi2d5p.c
5点差分(通信隠蔽無し)
1. コミュニケータ(cart2d)の定義
プロセス配置は2次元
プロセス並替えを許可
sizeを因数分解した値をdimsに返す
境界は非周期
182
49 // define derived datatypes 50 MPI_Datatype xghost, yghost; 51 MPI_Type_vector( 1, ny, ny+2, MPI_FLOAT, &xghost ); 52 MPI_Type_vector( nx, 1, ny+2, MPI_FLOAT, &yghost ); 53 MPI_Type_commit( &xghost ); 54 MPI_Type_commit( &yghost );
jacobi2d5p.c
nx
2. 派生データ型(xghost, yghost)の定義
yghost xghost
ny ny+2 ny+2
1
1
strid
e
strid
e
count count
blo
ck
len
gth
blo
ck
len
gth
注:データはY軸方向に連続
183
56 // send buffers // recv buffers 57 void *sbuf_n = &(phi[1][ny]); void *rbuf_n = &(phi[1][ 0]); 58 void *sbuf_s = &(phi[1][ 1]); void *rbuf_s = &(phi[1][ny+1]); 66 // shift northward 67 MPI_Sendrecv( sbuf_n, 1, yghost, north, 0 68 rbuf_n, 1, yghost, south, 0, cart2d, status ); 69 // shift southward 70 MPI_Sendrecv( sbuf_s, 1, yghost, south, 0, 71 rbuf_s, 1, yghost, north, 0, cart2d, status );
jacobi2d5p.c
3. Y軸方向の通信
184
southward Self
yghost
yghost
rbuf_s
sbuf_s
buffers
recv
south
north
send
northward Self
yghost
yghost
sbuf_n
rbuf_n
buffers
send
recv
south
north
59 void *sbuf_e = &(phi[nx][1]); void *rbuf_e = &(phi[ 0][1]); 60 void *sbuf_w = &(phi[ 1][1]); void *rbuf_w = &(phi[nx+1][1]); 72 // shift eastward 73 MPI_Sendrecv( sbuf_e, 1, xghost, east, 0 74 rbuf_e, 1, xghost, west, 0, cart2d, status ); 75 // shift westward 76 MPI_Sendrecv( sbuf_w, 1, xghost, west, 0, 77 rbuf_w, 1, xghost, east, 0, cart2d, status );
jacobi2d5p.c
4. X軸方向の通信
185
eastward
xg
ho
st
xg
ho
st
sbuf_e rbuf_e buffers
send
west
recv
Self east
westward
xg
ho
st
xg
ho
st
rbuf_w sbuf_w buffers
recv
west
send
Self east
67 // shift northward 68 MPI_Isend( sbuf_n, 1, yghost, north, 0, cart2d, &(req[0]) ); 69 MPI_Irecv( rbuf_n, 1, yghost, south, 0, cart2d, &(req[1]) ); 70 // shift southward 71 MPI_Isend( sbuf_s, 1, yghost, south, 0, cart2d, &(req[2]) ); 72 MPI_Irecv( rbuf_s, 1, yghost, north, 0, cart2d, &(req[3]) ); 73 // shift eastward 74 MPI_Isend( sbuf_e, 1, xghost, east, 0, cart2d, &(req[4]) ); 75 MPI_Irecv( rbuf_e, 1, xghost, west, 0, cart2d, &(req[5]) ); 76 // shift westward 77 MPI_Isend( sbuf_w, 1, xghost, west, 0, cart2d, &(req[6]) ); 78 MPI_Irecv( rbuf_w, 1, xghost, east, 0, cart2d, &(req[7]) );
jacobi2d5poverlap.c
5点差分(通信隠蔽有り)
• カーテシアン・トポロジと派生データ型は隠蔽無しと同じ
• 通信を隠蔽するために非閉塞通信を使い MPI_Waitall で完了する
• 完了するまでの間に領域内部の計算を行う
• 通信完了後に領域境界の計算を行う
1. Y-X軸方向の通信
186
80 // Jacobi iteration, interior 81 for( int i=2; i<=nx-1; ++i ) 82 for( int j=2; j<=ny-1; ++j ) 83 w[i][j] = 0.25*( phi[i+1][j ] + phi[i-1][j ] 84 phi[i ][j+1] + phi[i ][j-1] – h*h*f[i][j] ); 85 86 MPI_Waitall( 8, req, status );
jacobi2d5poverlap.c
2. 領域内部の計算と通信の完了
内部点
Y軸境界点
X軸境界点
計算領域の分類
0 1 2 ... nx nx+1 0
1
2
ny
ny+1
内部点のループ
187
88 // Jacobi iteration, fringe 89 const int jn = ny, js = 1; 90 for( int i=1; i<=nx; ++i ) 91 { 92 w[i][jn] = 0.25*( phi[i ][jn+1] + phi[i ][jn-1] 93 phi[i+1][jn ] + phi[i-1][jn ] – h*h*f[i][jn] ); 94 w[i][js] = 0.25*( phi[i ][js+1] + phi[i ][js-1] 95 phi[i+1][js ] + phi[i-1][js ] – h*h*f[i][js] ); 96 } 97 const int ie = nx, iw = 1; 98 for( int j=2; j<=ny-1; ++j ) 99 { 100 w[ie][j] = 0.25*( phi[ie ][j+1] + phi[ie ][j-1] 101 phi[ie+1][j ] + phi[ie-1][j ] – h*h*f[ie][j] ); 102 w[iw][j] = 0.25*( phi[iw ][j+1] + phi[iw ][j-1] 103 phi[iw+1][j ] + phi[iw-1][j ] – h*h*f[iw][j] ); 104 }
jacobi2d5poverlap.c
3. 領域境界の計算
X軸境界点
Y軸境界点
188
62 // initialize persistent sends 63 MPI_Request req[8]; 64 MPI_Ssend_init( sbuf_n, 1, yghost, north, 0, cart2d, &(req[0]) ); 65 MPI_Ssend_init( sbuf_s, 1, yghost, south, 0, cart2d, &(req[1]) ); 66 MPI_Ssend_init( sbuf_e, 1, xghost, east, 0, cart2d, &(req[2]) ); 67 MPI_Ssend_init( sbuf_w, 1, xghost, west, 0, cart2d, &(req[3]) ); 68 MPI_Recv_init( rbuf_n, 1, yghost, south, 0, cart2d, &(req[4]) ); 69 MPI_Recv_init( rbuf_s, 1, yghost, north, 0, cart2d, &(req[5]) ); 70 MPI_Recv_init( rbuf_e, 1, xghost, west, 0, cart2d, &(req[6]) ); 71 MPI_Recv_init( rbuf_w, 1, xghost, east, 0, cart2d, &(req[7]) );
jacobi2d5ppersistent.c
5点差分(持続通信)
• 前例とほとんど同じ
• 最初に送受信リクエストを作成しておく
• 通信は Startall で開始し Wailall で完了させる
1. 送受信リクエストの作成
• メモリコピーを回避するためここでは Synchronous send を使う
2. Y-X軸方向の通信
77 // start persistent sends 78 MPI_Startall( 8, req );
189
9点差分(通信隠蔽無し)
1. コミュニケータの定義は5点差分と同じ 2. 派生データ型は xghost を y 軸の全領域とする 3. この派生データ型の場合はY-Xの順で逐次的に通信する
190
49 // define derived datatypes 50 MPI_Datatype xghost, yghost; 51 MPI_Type_vector( 1, ny+2, ny+2, MPI_FLOAT, &xghost ); 52 MPI_Type_vector( nx, 1, ny+2, MPI_FLOAT, &yghost ); 53 MPI_Type_commit( &xghost ); 54 MPI_Type_commit( &yghost );
jacobi2d9p.c
nx
yghost xghost
ny+2 ny+2 ny+2
1
1
strid
e
strid
e
count count
blo
ck
len
gth
blo
ck
len
gth
56 // send buffers // recv buffers 57 void *sbuf_n = &(phi[1][ny]); void *rbuf_n = &(phi[1][ 0]); 58 void *sbuf_s = &(phi[1][ 1]); void *rbuf_s = &(phi[1][ny+1]); 66 // shift northward 67 MPI_Sendrecv( sbuf_n, 1, yghost, north, 0 68 rbuf_n, 1, yghost, south, 0, cart2d, status ); 69 // shift southward 70 MPI_Sendrecv( sbuf_s, 1, yghost, south, 0, 71 rbuf_s, 1, yghost, north, 0, cart2d, status );
jacobi2d9p.c
3. Y軸方向の通信
191
southward Self
yghost
yghost
rbuf_s
sbuf_s
buffers
recv
south
north
send
northward Self
yghost
yghost
sbuf_n
rbuf_n
buffers
send
recv
south
north
59 void *sbuf_e = &(phi[nx][0]); void *rbuf_e = &(phi[ 0][0]); 60 void *sbuf_w = &(phi[ 1][0]); void *rbuf_w = &(phi[nx+1][0]); 72 // shift eastward 73 MPI_Sendrecv( sbuf_e, 1, xghost, east, 0 74 rbuf_e, 1, xghost, west, 0, cart2d, status ); 75 // shift westward 76 MPI_Sendrecv( sbuf_w, 1, xghost, west, 0, 77 rbuf_w, 1, xghost, east, 0, cart2d, status );
jacobi2d9p.c
4. X軸方向の通信
192
eastward
xg
ho
st
xg
ho
st
sbuf_e rbuf_e buffers
send
west
recv
Self east
westward
xg
ho
st
xg
ho
st
rbuf_w sbuf_w buffers
recv
west
send
Self east
具体的な問題への応用
1. 構造格子系(差分法)
2. 非構造格子系(有限要素法,有限体積法)
3. フーリエ変換
4. MPI Tips
193
具体的な問題への応用
1. 構造格子系(差分法)
2. 非構造格子系(有限要素法,有限体積法)
3. フーリエ変換
4. MPI Tips
194
2次元フーリエ変換
195
• 2次元転置(2D transpose) 16 プロセスの場合
Transpose
Y to X
16 by 16
all-to-all
・配列全体をX軸で分割 ・配列全体をY軸で分割
・添字を巡回置換 (i-j⇒j-i)
x
y
Y FFT
A[i][j]
x
y
X FFT
B[j][i]
Transpose
ZY to YX
• 2次元転置(2D transpose)
Z-Y FFT X FFT
16 by 16
all-to-all
3次元フーリエ変換
196
x
z y
x
z y
A[i][j][k] B[j][k][i]
・配列全体をX軸で分割 ・配列全体をZ軸で分割
・添字を左巡回置換 (i-j-k⇒j-k-i)
16 プロセスの場合
Z FFT
Y FFT
X FFT
Transpose Z to Y
• 3次元転置(3D transpose)
部分コミュニケータ Cyz0 Cyz1 Cyz2 Cyz3
Cxy3
Cxy2
Cxy1
Cxy0
4 by 4 all-to-all を同時に4つ
部分コミュニケータ
197
x
z y
x
z y
x
z y
x
z y
A[i][j][k]
B[k][i][j]
C[j][k][i]
・配列全体をX軸で分割 ・添字を右巡回置換 (i-j-k⇒k-i-j)
・添字を右巡回置換 (k-i-j⇒j-k-i)
コミュニケータ切換え
Transpose Y to X
4 by 4 all-to-all を同時に4つ
・配列全体をY軸で分割
16 プロセスの場合
具体的な問題への応用
1. 構造格子系(差分法)
2. 非構造格子系(有限要素法,有限体積法)
3. フーリエ変換
4. MPI Tips
198
MPI通信の心得
1. 集団通信を使う ⇒ 通信手順の最適化
2. トポロジを使う ⇒ コミュニケータの最適化
3. 派生データ型を使う ⇒ メモリコピー削減 (システムによって悪くなる場合もある)
4. データはまとめて通信する ⇒ 回数削減, 長サイズ化
5. データ量の均一化 (BSP*の場合) ⇒ 無駄な待ち時間回避
*The MPE Multiprocessing Environment, Appendix C, Using MPI, 2nd ed., MIT Press, 1999 *http://www.mcs.anl.gov/research/projects/perfvis/download/
199 *Bulk Synchronous Parallel: barrierで計算と通信を分離する並列化手法
Appendix A
MPE(Multi-Processing Environment) Library
MPICHの開発グループが提供するユーティリティー群
(他のMPIライブラリからも利用可能)
以下の機能が提供されている
• Logging routines
• X window graphics
• Sequential section
• Shared counter
• Tag management
• Miscellaneous
Logging routines は性能プロファイリングに利用される
それ以外の機能はほとんど使われない
*The MPE Multiprocessing Environment, Appendix C, Using MPI, 2nd ed., MIT Press, 1999 *http://www.mcs.anl.gov/research/projects/perfvis/download/
200
#include “mpi.h” #include “mpe.h” MPI_Init( &argc, &argv); MPE_Init_log(); const int bgnID = 1; const int endID = 2; MPE_Describe_state(bgnID, endID, “Bcast”, “red:vlines3”); MPE_Log_event(bgnID, 0, “start”); // target section MPI_Bcast etc. MPE_Log_event(endID, 0, “stop”); MPE_Finish_log(“test.log”); MPI_Finalize();
Logging routinesの基本的な使い方
ヘッダファイルをインクルード
ログ取得開始
ログ取得終了, ファイル書出し
計時開始
計時終了
MPE_Describe_stateで計測領域の開始ID:bgnID,終了ID:endID, 領域名:“Bcast”,表示パターン:“red:vlines3” を定義する
ID番号は重複してはならない
201
SLOG-2形式のファイルを可視化するプログラム
下図のようにプロセス毎のタイムライン等を表示できる
Jumpshot-3 Visualization Program
202
実際には100プロセス・100秒程度まで それ以上は画面スクロール等の操作が困難
• Ubuntuのsynaptic(apt)は未だv1.6.5なので手動でインストール (注:既にMPIライブラリがインストールされている場合は完全にアンインストールしておく)
• 圧縮ファイル(openmpi-2.0.1.tar.gz)を
https://www.open-mpi.org/software/ompi/v2.0/ からダウンロード
• configureの質問はPCなら基本的にデフォルト設定
並列機の場合は色々設定する必要あり
• 作業には数時間~半日程度必要なこともある
• 最後の ldconfig はパスを通すため (注: ldconfigの前に/etc/ld.so.conf.d/libc.confの存在を確認しておく)
> tar xvf openmpi-2.0.1.tar.gz > cd openmpi-2.0.1 > sudo ./configure --prefix=/usr/local > sudo make all install > sudo ldconfig
Appendix B
Open MPI version 2.0.1のインストール方法
203
204
並列プログラミング入門(MPI編) はこれで終了です
お疲れ様でした