2.3.9. MPI_Isend と MPI_Irecv による相互通信

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <mpi.h>
#include <iostream>
#include <cstdlib>
#include <vector>

/*
 * 2つのプロセスの間で相互に配列データを送る
 *
 * 実行方法: mpiexec -n 2 mpi_sample9
 */
int main(int argc, char *argv[])
{
    MPI_Init(&argc, &argv);

    int num_procs;
    int my_rank;

    MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

    if (num_procs != 2) {
        std::cerr << "Run this program in MPI with 2 processes." << std::endl;
        return EXIT_FAILURE;
    }

    int send_data_count = 10;
    std::vector<double> send_data(send_data_count, 0);

    // それぞれのrankで送信するデータを用意する
    for (int i = 0; i < send_data_count; i++)
    {
        send_data[i] = (my_rank * 10) + i;
    }
    // 送信データを表示する
    std::cout << "rank: " << my_rank << ", send_data: [";
    for (int i = 0; i < send_data_count; i++)
    {
        std::cout << " " << send_data[i];
    }
    std::cout << " ]" << std::endl;

    int recv_data_count = 10;
    std::vector<double> recv_data(recv_data_count, 0);

    // ノンブロッキングの send/recv を使って、相互にデータを送受信する
    // 互いに同時に送信しても問題ない
    MPI_Request requests[2];
    MPI_Status statuses[2];

    int other_rank = num_procs - 1 - my_rank;

    MPI_Isend(
        &send_data[0],
        send_data_count,
        MPI_DOUBLE,
        other_rank, // 送り先のrank
        0, // タグ。データをプログラムで区別するための番号。送信側と受信側で一致している必要がある。
        MPI_COMM_WORLD,
        &requests[0] // このノンブロッキング操作に対して発行されるIDをrequestsに受け取る
    );
    // この時点で2つのプロセスのそれぞれが送信しようとするが、
    // まだどちらも受信しようとしていない。
    // もしここでブロッキング関数をMPI_Sendを使っていたら、
    // ここでどちらも送信が完了するまで MPI_Send 関数から帰ってこないで
    // デッドロックになる。MPI_Isendならば、sendが完了しなくても関数から
    // 帰ってくる。

    MPI_Irecv(
        &recv_data[0],
        recv_data_count,
        MPI_DOUBLE,
        other_rank, // 送り元のrank
        0, // タグ。送信側でつけた番号と同じもの
        MPI_COMM_WORLD,
        &requests[1] // このノンブロッキング操作に対して発行されるIDを記録する
    );

    // ノンブロッキング操作が完了するまで待つ。
    // どの操作が完了するまで待つのかを、MPI_Request の配列で指定する。
    // 操作ごとに失敗する可能性があるので、操作ごとの実行結果をMPI_Statusの
    // 配列で受け取る

    MPI_Waitall(
        2, // 操作の数
        &requests[0],
        &statuses[0]
    );

    // 受信できたデータを表示する
    std::cout << "rank: " << my_rank << ", recv_data: [";
    for (int i = 0; i < recv_data_count; i++)
    {
        std::cout << " " << recv_data[i];
    }
    std::cout << " ]" << std::endl;

    MPI_Finalize();
    return EXIT_SUCCESS;
}

Download the source code

複数のプロセスの間で相互にデータを送り合いたい場合がありますが、 そのような場合に同期型の MPI_Send, MPI_Recv を使うと問題となる 場合があります。MPI_Send は、相手プロセスが MPI_Recv を発行する まで終わりません。ブロックする、という言い方をします。

互いにデータを送りたいプロセスがみなこぞって、まず先に、 自分が送り出したいデータを MPI_Send で送ろうとすると、 誰も MPI_Recv を呼び出さないので MPI_Send が先に進まず、 全てのプロセスがブロックしてしまいます。

そのような場合には、ノンブロッキングの MPI_Isend, MPI_Irecv を使うと 問題を回避できます。MPI_Isend は、通信相手が MPI_Irecv を発行していな くてもとりあえず関数としては終わって帰ってきます。つまり、ブロックしません。

帰ってきた後で、MPI_Irecvを発行することができます。

一通りのMPI_IsendとMPI_Irecvを発行し終えた後でMPI_Waitallを呼ぶと、 そこで全てのMPI_IsendとMPI_Irecvが完了するまで待ちます。ここはブロックします。

一回一回のMPI_Isend, MPI_Irecv の呼び出しの度に、宅配便の控え伝票の ようなデータである MPI_Request という型の値が渡されますので、この値を 必ず保持しておいて、MPI_Waitallに渡すようにします。

このサンプルでは2つのプロセスの間で相互にデータを送り合います。 3つ以上のプロセスに一般化した例は次のサンプルにあります。

openmpi上での実行例を示します。:

$ mpic++ -o mpi_sample9 mpi_sample9.cpp
$ mpiexec -n 2 mpi_sample9
rank: 0, send_data: [ 0 1 2 3 4 5 6 7 8 9 ]
rank: 1, send_data: [ 10 11 12 13 14 15 16 17 18 19 ]
rank: 1, recv_data: [ 0 1 2 3 4 5 6 7 8 9 ]
rank: 0, recv_data: [ 10 11 12 13 14 15 16 17 18 19 ]