並列プログラミング技法( 詳説)...参考図書等 ・using mpi, third edition w....

205
並列プログラミング技法(MPI詳説) 高度情報科学技術研究機構 神戸センター 宮内 0

Upload: others

Post on 09-Jul-2020

14 views

Category:

Documents


0 download

TRANSCRIPT

  • 並列プログラミング技法(MPI詳説)

    高度情報科学技術研究機構 神戸センター 宮内 敦

    0

  • 本日の講義内容

    ⚫MPIの基礎知識

    ⚫MPI関数各論

    ⚫並列性能評価

    ⚫具体的な問題への応用

    1

  • ⚫MPIの基礎知識

    1. MPI規格 (MPI standard)

    2. MPIライブラリ

    2

  • •プロセス並列を前提としたプロセス間のメッセージ交換に関する標準仕様

    •適用対象は分散記憶アーキテクチャー上の分散アドレス空間におけるSPMDモデル

    •ネットワーク接続された異機種クラスター環境にも対応

    •先行したPVM(Parallel Virtual Machine)に不足し

    ていた機能を採り入れている

    •現在ではスレッド並列・直接メモリアクセス・MPMDの機能も提供されている

    ◼MPI 規格(MPI standard)とは

    3

  • ◼規格の内容 (Version 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)4

  • ◼注意すべき用語・バッファ(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, 19995

  • ◼参考図書等

    ・Using MPI, Third EditionW. Gropp, E. Lusk and A. Skjellum, MIT press, 2014

    ・Using Advanced MPIW. Gropp, T. Hoefler, R. Thakur and E. Lusk, MIT press, 2014

    ・Parallel Programming in C with MPI and OpenMPM. J. Quinn, McGraw-Hill, 2008

    ・Parallel Programming with MPIP. Pacheco, Morgan Kaufmann, 1996

    ・Tutorials*http://www.mcs.anl.gov/research/projects/mpi/tutorial/

    *query words = MPI, parallel computing, high performance computing, message passing, etc.

    6

  • ⚫MPIの基礎知識

    1. MPI規格(MPI Standard)

    2. MPIライブラリ

    7

  • ◼特徴・MPI Standardに準拠して開発される・プロセス並列モデル・プロセス間通信でメッセージ交換・オープンソフトから商用ソフトまで多数・ライブラリにより性能の違いやバグ

    ◼代表的ライブラリ・OpenMPI FT, LA, LAM joint team ☆現在の最大勢力・MPICH* Argonne National Lab. ☆根強い人気・MVAPICH Ohio State Univ. ☆GPUに強み・LAM/MPI Indiana Univ. ★かつてクラスタ向けに人気・CHIMP Edinburgh P.C.C. ★初期のライブラリ・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

    8

  • ◼実装状況(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

    9

  • ◼命名規則(Naming conventions)他

    ⚫ 全ての名前は接頭辞 MPI_ で始まる

    ⚫ 定数・データ型・状態・演算等は全て大文字

    例: MPI_COMM_WORLD, MPI_INT, MPI_SUCCESS, MPI_SUM, etc.

    ⚫ 関数は最初の一文字のみ大文字、残りは全て小文字

    例: MPI_Comm_rank(), MPI_Init(),

    MPI_Send(), MPI_Bcast(), etc.

    ⚫ 通信が成功した時の戻り値は MPI_SUCCESSif(MPI_Xxx())は期待通りに動作しない場合あり

    失敗したときの値も実装依存10

  • ⚫MPI関数各論

    1. 集団通信

    2. 要素通信

    3. 片側通信

    4. マルチスレッド

    5. 派生データ型

    6. ファイルI/O

    7. コミュニケータ・トポロジー

    8. プロセス生成

    9. その他

    11

  • ◼集団通信とは

    ⚫ コミュニケータ内の多プロセス間で一斉に行う通信⚫ 内部で要素通信を呼ぶことで実装されている

    ◼注意点

    ⚫ コミュニケータ内の全プロセスで関数呼出しが必要

    ⚫ 実際に全てのプロセスが送受信するとは限らない

    ⚫ 引数の値は基本的に全てのプロセスで同じ

    ⚫ root, comm は全てのプロセスで同じ値を指定する

    ⚫ 送受信バッファはプロセス毎に異なっても良い

    ⚫ 関数名にvの付くものはプロセス毎にデータ数が異なる(bcast/scatter/scatterv/gather/gatherv/reduceでは

    使用しないバッファに MPI_BOTTOM を指定できる)

    12

  • ◼集団通信関数一覧

    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

    ✔✔ ✔✔ ✔✔ ✔✔ ✔✔ ✔✔✔✔ ✔✔ ✔✔ ✔✔ ✔✔ ✔✔ ✔✔ ✔✔ ✔

    IntercommMPI_IN_PLACE

    13

  • ◼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 の持つデータを全プロセスにコピーする

    14

    注: 本講習では引数の色は変数の属性を示し 入力、出力、入出力 を表す

  • ◼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 の持つデータを全プロセスに分配する

    15

  • ◼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)

    16

  • ⚫MPI_Scatter/MPI_Scatterv の注意点

    MPI_Scatterv は MPI_Scatter をデータ長可変にしたもの

    送信側パラメータ (sendbuf, sendcount, sendcounts[], displs[], sendtype)

    • root の値のみ有効、それ以外の値は無視される

    受信側パラメータ (recvbuf, recvcount, recvtype)

    • recvcount は受信するデータ長以上であれば良い• recvcount が受信するデータ長未満の場合はエラー• Scatterv の recvcount は本来プロセス毎に異なる

    通常は recvcount=sendcounts[rank] とする(前頁は全て3でも正常動作)

    • recvbuf の配列長は recvcount 以上必要• recvbuf の不要な配列要素は無視される• recvtype と sendtype は異なっても良いが

    受信データ長が送信より少ないとエラー

    分割されたデータの送信先はプロセス番号順Scattervではdisplsの値によって非連続的な順番も可能但しデータに重なりあるとエラーになるので注意

    17

  • ◼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 に収集する

    18

  • ◼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)

    19

  • ⚫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 は異なっても良いが

    受信データのバイト数が不足するとエラー

    受信データの開始位置はプロセス番号順Gathervではdisplsの値によって非連続的な順番も可能但しデータに重なりあるとエラーになるので注意

    20

  • ◼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 に収集して総和

    21

  • ◼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)

    22

  • ◼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)

    23

  • ⚫MPI_Reduce/MPI_Scan/MPI_Exscanの注意点

    受信バッファ(recvbuf) の値

    • MPI_Reduce では root のみ値が返される• MPI_Scan では全プロセスで値が返される• MPI_Exscan ではプロセス0以外で値が返される• 値の返されない recvbuf の内容は通信の前後で変わらない

    24

  • int in, out;in = 10; // local array sizeout = 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.outrank 0 begins from 0rank 1 begins from 10rank 2 begins from 20rank 3 begins from 30>

    25

  • 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

    ✔ ✔ ✔

    ✔ ✔

    ✔ ✔

    ✔ ✔

    最大値

    最小値

    最大値と指標

    最小値と指標

    総和

    総積

    論理積

    論理和

    論理排他和

    ビット積

    ビット和

    ビット排他和

    26

  • 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].indexProcess0 1.2 3 4.2 9Process1 3.5 4 1.3 2Process2 2.3 1 8.4 7Process3 4.8 0 5.7 3

    out[0].value out[0].index out[1].value out[1].indexProcess1 4.8 0 8.4 7

    プロセス1には以下の値が格納される

    最大の実数値とその指標を探す

    • 指標は必ず整数• valueが整数の場合はMPI_2INT型(MPI_INT_INTではない)

    Example (maxloc.c)

    27

  • ⚫ユーザ定義演算

    typedef struct{ float vx, vy; } fvec2;

    void addvec(void *invec, void* iovec, int *len, MPI_Datatype *dptr){

    fvec2 *a = (fvec2 *)invec; // in vectorfvec2 *b = (fvec2 *)iovec; // inout vectorfor(int i=0; ivx += 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が代入される

    28

  • ◼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

    29

  • ◼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)

    30

  • ◼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)

    31

  • ◼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 25 1 63 7

    0 1 2 3 4 5 6 7

    P0

    P1

    P2

    P3

    sbuf 2 3 01 4 65 7

    0 1 2 3 4 5 6 7

    sbuf 5 7 62 3 01 4

    0 1 2 3 4 5 6 7

    sbuf 7 0 14 5 36 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 1214 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)

    32

  • ◼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 25 1 63 7

    0 1 2 3 4 5 6 7

    P0

    P1

    P2

    P3

    sbuf 2 3 01 4 65 7

    0 1 2 3 4 5 6 7

    sbuf 5 7 62 3 01 4

    0 1 2 3 4 5 6 7

    sbuf 7 0 14 5 36 2

    0 1 2 3 4 5 6 7

    sum

    14 15 1214 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

    9sbuf

    0 1 2

    14 20

    sbuf

    0 1 2

    13 15

    Function prototype

    Example (reducescatter.c)

    33

  • ◼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)

    34

  • ◼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 rcountsProcess0 { 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

    35

  • 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 rdisplsProcess0 { 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)

    36

  • ◼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

  • ◼MPI_Barrier

    int MPI_Barrier( MPI_Comm comm )

    ⚫ 全プロセスがコールするまで待ち合わせる

    ⚫ 全プロセスが同時に復帰する訳ではない(タイムラグ有り)

    ⚫ ハードウェアによって高速化している場合もある

    MPI_Barrier(MPI_COMM_WORLD);

    Function prototype

    Example

    38

  • 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

    39

  • ◼非閉塞(Nonblocking)通信

    ⚫ 通信相手の状態に関係なく即座に復帰

    ⚫ 後からTest/Wait関数で完了

    ⚫ ノード内に通信用コプロセッサ等を持つ場合にはLinuxカーネルを改造し通信処理をオフロード(off-load)することで通信の隠蔽(overwrap)が可能な場合あり*

    ⚫ オフロードできない場合はTest/Wait関数の中で通信を開始するので通信は隠蔽できない(multi-thread化すれば隠蔽可能)

    ⚫ 非閉塞通信は本来デッドロックを避けるためMPI standardの中に隠蔽に関する規定は無い

    *With suitable hardware, transfer … may proceed concurrently with computations ... , p.47, line 20, MPI Standard 3.1

    *whether such overlapping is possible may depend on the hardware …, p.108, line 11, Using MPI, 3rd ed., MIT Press, 2014

    40

  • int buf[3] = ... , sbuf[2] = ... , rbuf[2] = ... ; // buffersMPI_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

    41

  • 42

    A busy wait B

    A B

    MPI_Barrier

    MPI_Barrier

    Process 0

    Process 1

    time

    仮定:処理AとBは同時に実行できない

    ➡ Bは同時に開始するが busy wait が発生する

    ➡ busy wait は解消するが Bは同時に開始しない

    A B

    A C

    MPI_Ibarrier

    MPI_Ibarrier

    Process 0

    Process 1

    time

    C

    MPI_Wait

    B

    MPI_Wait

    ⚫非閉塞Barrierの使い方

    1. blocking-barrierで分離する (Bulk Synchronous)

    2. A,Bと同時実行可能なCを挟んでnon-blocking barrierで分離する

  • ◼送受信バッファの共通化

    ⚫ 送信バッファに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

    ✓ 非閉塞通信も閉塞通信と全く同じ✓ Gather, Gathervではrootのデータは既にrecvbufに入っていると仮定される

    43

  • ⚫MPI関数各論

    1. 集団通信

    2. 要素通信

    3. 片側通信

    4. マルチスレッド

    5. 派生データ型

    6. ファイルI/O

    7. コミュニケータ・トポロジー

    8. プロセス生成

    9. その他

    44

  • ◼要素通信とは

    ⚫ 最も基本的な通信方法⚫ コミュニケータ内の一対のプロセス間の通信⚫ 一方が送信し、他方が受信する⚫ 送信側が制御する push mechanism⚫ 送受信を一つにまとめた複合通信が用意されている⚫ オーバーヘッドが軽い持続通信が用意されている

    ◼注意点

    ⚫ 送信側にはモードがある⚫ 不適切なモード選択はデッドロックを引き起こす

    45

  • ◼要素通信関数一覧

    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

    46* 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

    47

    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からなる構造体 MPI_Sendのbuf以外の引数をエンベロープ(envelope)と呼ぶ

    48

    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

    正常終了

    ① ②

    ③④

    49

    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が復帰(完了)

    50

    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

    RecvRecv

    ②①

    互いに相手を待ち続ける

    51

    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

    ⑥⑤

    ⑨ ⑩

    ⑧⑦

    データが小さい場合(

  • 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

    SendSend

    ②①

    互いに相手を待ち続ける

    53

    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)

    54

  • • Standard

    ➢ データが小さい場合はシステムバッファにバッファリング

    ➢ データが大きい場合は受信側とハンドシェイク

    • Synchronous

    ➢ バッファリングせず受信側とハンドシェイク

    ➢ 最もデッドロック起き易い

    ➢ このモードでデッドロックしないプログラムをsafe programと呼ぶ

    • Ready

    ➢ 受信側の状態に関係なく即座に送信

    ➢ 受信側が既に待機している場合のみ開始可能

    ➢ それ以外はエラーまたは不定状態

    • Buffered

    ➢ ユーザが設定した添付バッファにバッファリング

    ⚫各モードの特徴

    55

  • • MPICHでは2つの通信プロトコルを定義*

    • Eager protocolエンベロープに続けてデータも送信

    • Rendezvous protocolエンベロープだけ送り、準備が整ったらデータを送信

    • 送信関数の引数

    * MPI standardに規定はない

    ⚫通信モードの実装方法

    56

    MPI_Send( buf, 2, MPI_INT, 1, 9, MPI_COMM_WORLD );

    data** envelope

    message

    ** buf はデータへのポインタ

  • 57

    ⚫ Eager protocol

    a) recv先行

    b) send先行

    • recv 側の基本動作

    ◼ 未処理の recv を expected queue に登録◼ 未処理の send を unexpected queue に登録◼ send が到着すると expected queue に matching recv を探す◼ recv が発行されると unexpected queue に matching send を探す

    send recv unexpected queue

    expected queue

    inquire

    no matching send

    register recvinquire

    matching recv exists

    rbufstore

    send

    recv unexpected queue

    expected queue

    register send

    matching send exists

    inquire

    no matching recv

    rbufcopy

    sysbufstore

    inquire

  • 58

    ⚫ Rendezvous protocol

    c) recv先行

    d) send先行

    send recv unexpected queue

    expected queue

    inquire

    no matching send

    register recvinquire

    matching recv exists

    rbufstore

    send

    recv unexpected queue

    expected queue

    inquire

    register send

    inquire

    no matching recv

    rbufstore

    matching send exists

  • • Standard (短メッセージはバッファリング有り、Eagerそれ以外はバッファリング無し、Rendezvous)

    • Synchronous (バッファリング無し、Rendezvous)

    • Ready (バッファリング有り、Eager)注:受信側のシステムバッファを使う、小さいとエラー

    • Buffered (ユーザ定義バッファ有り、 Rendezvous)注:ユーザ定義バッファ送信側で設定しサイズの指定が可能

    ⚫通信モードと protocol の対応例

    59

  • 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

    ⑥④

    ⑧ ⑨

    ⑦⑤

    ◼通信モードを使ってデッドロックを避ける

    60

    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 10000int 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はポインタ型!

    61

    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

    62

    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 )

    63

    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通信を使う!64

    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 )

    65

    Example (mode05.c)

    Function prototype

  • 66

    ◼完了関数の基本動作

    • 閉塞通信では復帰=完了なので不要• 非閉塞通信(持続通信も含む)では

    通信ハンドル(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_StatusMPI_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[] )

    67

    ◼完了関数の引数一覧

    • 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 の内容は不定(記述無し)

  • 68

    ◼完了関数の引数一覧(続き)

    • 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_StatusMPI_Cancel ( *request, ----- ----- )MPI_Request_free ( *request, ----- ----- )MPI_Request_get_status ( request, *flag, *status )

    const MPI_Status intMPI_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 )

    69

    Function prototype

    ⚫ 自プロセス内の送信と受信を一つの関数で実行する

    一対の送受信ではないことに注意

    ⚫ 通信相手側は通常の send, recv で受けても良い

    ⚫ バッファを共通化した関数も用意されている (MPI_Sendrecv_replace)

    ⚫ 非閉塞版はない (デッドロックしないので必要ない)

    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)

    70

    Function prototype

    ⚫ 最初に作った request を何度も再利用する

    ⚫ 通信開始は request を start させるだけでよい

    ⚫ 非閉塞通信と同様に test, wait を呼んで完了させる

  • 71

    int sbuf[100], rbur[100];MPI_Request req[2];

    MPI_Send_init( sbuf, 100, MPI_INT, right, 0, MPI_COMM_WORLD, &(req[0]) ); MPI_Recv_init( rbuf, 100, MPI_INT, left, 0, MPI_COMM_WORLD, &(req[1]) );

    for(int n=0; n

  • ⚫ データを一斉に隣のプロセスに送るシフト通信の場合両端のプロセスは送信または受信のみとなる

    ⚫ 送受信関数の 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 = ( rank0 ? 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 72

  • ⚫ マスターワーカモデル(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 );

    73

  • ⚫ 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はマルチスレッド実行時に問題あり74

  • ⚫MPI関数各論

    1. 集団通信

    2. 要素通信

    3. 片側通信

    4. マルチスレッド

    5. 派生データ型

    6. ファイルI/O

    7. コミュニケータ・トポロジー

    8. プロセス生成

    9. その他

    75

  • ◼片側通信とは

    ⚫ 各プロセスのメモリ上にウィンドウ(window)と呼ばれる公開領域を設定し、この領域に対して送受信する

    ◼注意点

    ⚫ ウィンドウ初期化、送受信、同期の3ステップがある

    ⚫ 送受信関数の呼び出しは送信側または受信側のみ

    ⚫ 比較・演算を組合わせた送受信も可能

    ⚫ 送受信関数には request を返すものもある

    ⚫ 通信は request が無い場合でも同期が必要

    ⚫ 同期方法には2種類の能動的同期(active target

    synchronization)と1種類の受動的同期(passive target

    synchronization)がある

    76

  • ◼片側通信関数一覧

    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

    77

    MPI_Win_create

    MPI_Win_create_dynamic + MPI_Win_attach/detach

    MPI_Win_allocate

    MPI_Win_allocate_shared*

    MPI_Win_free

    * MPI_Win_allocate_shared, MPI_Win_shared_queryはマルチスレッドのページで説明する

    Query MPI_Win_shared_query*

  • ◼片側通信関数一覧(続)

    Synchro-

    nization

    78

    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*

    InfoMPI_Win_set_info*

    MPI_Win_get_info*

    * MPI_Win_get_group, MPI_Win_set_info, MPI_Win_get_infoの説明は省略

  • 79

    ◼初期化関数の引数

    • どちらも local call、size はバイト数指定• base, size はプロセス毎に異なってもよい• MPI_Win_detach の引数 *base は正確には const void

    MPI_Win void MPI_AintMPI_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_create_dynamic ----- ----- ----- info comm ----- *win

    MPI_Win_allocate ----- size disp_unit info comm *baseptr *win

    MPI_Win_allocate_shared ----- size disp_unit info comm *baseptr *win

    MPI_Free ----- ----- ----- ----- ----- ----- *win

  • 80

    ◼送受信関数の引数

    • 関数の呼出し側(caller)プロセスを origin、相手側(callee)を target と呼ぶ• データ移動の始点を source、終点を destination と呼ぶ

    ➢ MPI_Put ⇒ origin=source, target=destination➢ MPI_Get ⇒ origin=destination, target=source

    • target 側は memory window 内のデータのみアクセス可能、origin 側は制約なし

    functionorigin 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

  • 81

    ◼送受信関数の引数(続)

    • 動作は非閉塞的であり、必ず同期が必要• 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 でしか使えない)

  • 82

    ◼同期関数の引数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 -----

  • 83

    ◼同期の方法

    ⚫ Active target:• origin と target の両側で同期関数を呼ぶ• origin は access epoch の間だけ通信できる• target は exposure epoch の間だけウィンドウを公開する• 以下の2種類の方法がある

    a. MPI_Win_fenceb. 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)

  • 84

    ◼ 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 epochbuffer

  • 85

    ◼ 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 epochbuffer

    *MPI_Win_test はMPI_Win_wait の非閉塞版

  • 86

    ◼ 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)に使用する場合は type = 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. その他

    87

  • ◼マルチスレッド時のMPI関数実行

    ⚫ MPIではマルチスレッド時の実行に関して

    低い順に以下の4つのレベルが設定されている

    ⚫ 実行環境により設定可能なレベルは異なる

    • MPI_THREAD_SINGLE

    シングルスレッド時のみ実行可能

    • MPI_THREAD_FUNNELED

    マルチスレッド時はマスタースレッドのみ実行可能

    • MPI_THREAD_SERIALIZED

    マルチスレッド時は同時に一つのスレッドのみ実行可能*

    • MPI_THREAD_MULTIPLE

    マルチスレッド時も制約なしにどのスレッドも実行可能

    *スレッド間の排他制御はユーザの責任

    88

  • ⚫ レベル指定付き初期化

    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)

    ⚫ 問い合わせ関数

    89

  • ⚫スレッド共有記憶の設定

    90

    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 はマルチスレッド実行の時に使う

  • ⚫スレッド共有記憶の設定

    91

    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 で割当てたメモリウィンドウの情報を返す

  • ◼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

    92

  • 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)

    93

  • ⚫MPI関数各論

    1. 集団通信

    2. 要素通信

    3. 片側通信

    4. マルチスレッド

    5. 派生データ型

    6. ファイルI/O

    7. コミュニケータ・トポロジー

    8. プロセス生成

    9. その他

    94

  • ◼派生データ型とは

    ⚫ 基本型を組み合わせてユーザが定義する構造を持ったデータ型

    ⚫ ギャップを挟んだデータや複数の型をメンバーにもつ構造体なども定義できる

    ⚫ さらに派生データ型を組み合わせた派生データ型も定義可能

    ◼注意点

    ⚫ 関数名に一部整合的でないものが存在する(動詞Createが欠けている)

    ⚫ 長さの指定が要素数の場合とバイト数の場合がある⚫ ギャップを間引いて圧縮したい場合は MPI_Pack で

    圧縮し MPI_Unpack で復元する

    95

  • ◼派生データ型関数一覧

    Definition

    96

    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の説明は省略

  • ◼派生データ型関数一覧(続)

    97

    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 個連接した型を定義する

    98

    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 の頭文字)

    99

    newtype = int int int int int int int int int int

    blocklength

    count

    stride

  • ◼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 はバイト数

    100

    newtype = int int int int int int int int int

    blocklength

    count

    displacements[0] displacements[1]

  • ◼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 はバイト数

    101

    newtype = int int int int int int int int int int

    blocklengths[0]

    count

    displacements[0]

    blocklengths[1] blocklengths[2]

    displacements[1]

  • ◼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 の単位はバイト数

    102

    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)

    • 派生データ型を通信に使えるよう有効化する

    103

    ◼MPI_Type_free

    int MPI_Type_free(MPI_Datatype *datatype)

    MPI_Datatype newtype;

    MPI_Type_free(&newtype);

    Function prototype

    Example (-----.c)

    • 派生データ型を解放する(deallocateする)

    * MPI_Type_create_subarray, MPI_Type_create_darray はファイルI/Oのページで説明する

  • ◼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 を返す

    104

    float in0[2] = {0.1, 0.2};

    int in1[3] = {11, 12, 13};int position = 0;

    char out[1000];

    MPI_Pack(in0, 2, MPI_FLOAT, out, 1000, &position, MPI_COMM_WORLD);MPI_Pack(in1, 3, MPI_INT, out, 1000, &position, MPI_COMM_WORLD);

    MPI_Send(out, position, MPI_PACKED, 1, 9, MPI_COMM_WORLD);

    Example (pack.c)

    outbuf = float int intfloat int

    1000 bytes

    incount

    float floatinbuf =

    incount

    intinbuf = int int

    position position position

  • Function prototype

    105

    ◼MPI_Unpack

    • inbuf から始まる長さ insize のバッファのカレントポインタ position から、outcount 個のdatatype 型データを pop して outbuf に格納する

    • MPI_recv の recvsize が position でないことに注意

    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_Recv(in, 1000, MPI_PACKED, 0, 9, MPI_COMM_WORLD, MPI_STATUS_IGNORE);MPI_Unpack(in, 1000, &position, out0, 2, MPI_FLOAT, MPI_COMM_WORLD);

    MPI_Unpack(in, 1000, &position, out1, 3, MPI_INT, MPI_COMM_WORLD);

    Example (pack.c)

    outcount

    float floatoutbuf =

    outcount

    intoutbuf = int int

    inbuf = float int intfloat 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 に返す

    106

    ◼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. その他

    107

  • #define BUFSIZE 100int main( int argc, char *argv[] ){

    int rank, buf[BUFSIZE];char fname[128];

    MPI_Init( &argc, &argv );MPI_Comm_rank( MPI_COMM_WORLD, &rank );sprintf( fname, “myfile%.4d”, rank );

    FILE *fp = fopen( fname, “wb” );fwrite( buf, sizeof(int), BUFSIZE, fp );fclose( fp );

    MPI_Finalize();return 0;

    }

    b. 書式無し

    108

    a. 書式付き

    #define BUFSIZE 100int main( int argc, char *argv[] ){

    int rank, buf[BUFSIZE];char fname[128];

    MPI_Init( &argc, &argv );MPI_Comm_rank( MPI_COMM_WORLD, &rank );sprintf( fname, “myfile%.4d”, rank );

    FILE *fp = fopen( fname, “wt” );for( int i=0; i

  • 109

    ◼MPI_I/Oを用いる場合

    ⚫ プロセス毎にファイルを作る (MPI_COMM_SELF)

    ⚫ 書式無し(unformatted file)のみ可能

    #define BUFSIZE 100int main( int argc, char *argv[] ){

    int rank, buf[BUFSIZE];char fname[128];

    MPI_Init( &argc, &argv );MPI_Comm_rank( MPI_COMM_WORLD, &rank );sprintf( fname, “myfile%.4d”, rank ); // myfile0000, myfile0001, myfile0002 …

    MPI_File fp;MPI_File_open( MPI_COMM_SELF, fname, MPI_MODE_WRONLY|MPI_MODE_CREATE,

    MPI_INFO_NULL, &fp );MPI_File_write( fp, buf, BUFSIZE, MPI_INT, MPI_STATUS_IGNORE );MPI_File_close( fp );

    MPI_Finalize();return 0;

    }

    c. 書式無し分離ファイル

  • 110

    ⚫ コミュニケータ内に1つだけファイルを作る (MPI_COMM_WORLD)

    ⚫ 書式無し(unformatted file)のみ可能

    ⚫ プロセス毎に書込み位置を指定する (MPI_File_seek)

    #define BUFSIZE 100int main( int argc, char *argv[] ){

    int rank, buf[BUFSIZE];char fname[128];

    MPI_Init( &argc, &argv );sprintf( fname, “myfile”, rank ); // myfile only

    MPI_File fp;MPI_File_open( MPI_COMM_WORLD, fname, MPI_MODE_WRONLY|MPI_MODE_CREATE,

    MPI_INFO_NULL, &fp );MPI_File_seek( fp, rank*BUFSIZE, MPI_SEEK_SET );MPI_File_write( fp, buf, BUFSIZE, MPI_INT, MPI_STATUS_IGNORE );MPI_File_close( fp );

    MPI_Finalize();return 0;

    }

    d. 書式無し単一ファイル (single file)

  • ◼ファイル操作関数

    111

    MPI_File_open, MPI_File_close, MPI_File_delete,

    MPI_File_seek, MPI_File_seek_shared,

    MPI_File_get_position, MPI_File_get_position_shared,

    MPI_File_set_view, MPI_File_get_view,

    MPI_File_set_size, MPI_File_get_size, MPI_File_preallocate,

    MPI_File_set_info, MPI_File_get_info,

    MPI_File_get_amode,

    MPI_File_get_group,

    MPI_File_get_byte_offset,

    MPI_File_set_atomicity, MPI_File_get_atomicity, MPI_File_sync,

    MPI_File_get_type_extent, MPI_Register_datarep,

    注:ファイルはindividual file pointerとshared file pointerを持つ

  • ⚫ 各プロセスが書き込み可能なファイルの領域

    ⚫ ファイル先頭からdispバイト後に置かれるfiletypeの繰り返し

    ⚫ dispとfiletypeはプロセス毎に異なってもよい

    ⚫ filetypeはetype(char, int, float etc.)の列と空白領域を含む

    ⚫ MPI_File_set_viewで設定する

    112

    ◼ファイルビュー

    etype filetype

    lb(bytes)

    extent(bytes)hole

    hole

    fileviewdisp

    file

  • ◼ 個別ファイルポインタ (individual file pointer)

    ◼ 陽的オフセット (explicit offset)

    ◼ 共有ファイルポインタ (shared file pointer)

    113

    独立I/O (independent I/O)

    集団I/O (collective I/O)

    分割集団I/O (split collective I/O)

    ◼データアクセス関数

    Blocking Nonblocking

    Read

    Write

    MPI_File_read

    MPI_File_read_at

    MPI_File_read_shared

    MPI_File_read_all

    MPI_File_read_at_all

    MPI_File_read_ordered

    MPI_File_write

    MPI_File_write_at

    MPI_File_write_shared

    MPI_File_write_all

    MPI_File_write_at_all

    MPI_File_write_ordered

    MPI_File_iread

    MPI_File_iread_at

    MPI_File_iread_shared

    MPI_File_read_all_begin/end

    MPI_File_read_at_all_begin/end

    MPI_File_read_ordered_begin/end

    MPI_File_iwrite

    MPI_File_iwrite_at

    MPI_File_iwrite_shared

    MPI_File_write_all_begin/end

    MPI_File_write_at_all_begin/end

    MPI_File_write_ordered_begin/end

  • 114

    ◼ 個別ファイルポインタ各プロセスが自身のファイルポインタを持つ(順方向に自動更新)

    ◼ 陽的オフセットファイルポインタを用いず直接に任意の位置を指定する

    ◼ 共有ファイルポインタコミュニケータ内のプロセスは一つのファイルポインタを共有する

    注:ファイルポインタは最後に読書きした位置を保持する

    file

    process0 process2 process1

    file

    process0

    seekprocess2process1

    seekseek

    注:MPI_File_seek で位置を指定する

    file

    process0

    file

    process2

    file

    process1

    tim

    e

    注:複数のプロセスが同時に読書きできない、全てのプロセスが同じファイルビューを持っていないとエラー

  • 115

    *独立I/Oよりも集団I/Oの方が読書きが速い(数倍~数十倍速い