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

 1#include <mpi.h>
 2#include <iostream>
 3#include <cstdlib>
 4#include <vector>
 5
 6/*
 7 * 2つのプロセスの間で相互に配列データを送る
 8 *
 9 * 実行方法: mpiexec -n 2 mpi_sample9
10 */
11int main(int argc, char *argv[])
12{
13    MPI_Init(&argc, &argv);
14
15    int num_procs;
16    int my_rank;
17
18    MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
19    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
20
21    if (num_procs != 2) {
22        std::cerr << "Run this program in MPI with 2 processes." << std::endl;
23        return EXIT_FAILURE;
24    }
25
26    int send_data_count = 10;
27    std::vector<double> send_data(send_data_count, 0);
28
29    // それぞれのrankで送信するデータを用意する
30    for (int i = 0; i < send_data_count; i++)
31    {
32        send_data[i] = (my_rank * 10) + i;
33    }
34    // 送信データを表示する
35    std::cout << "rank: " << my_rank << ", send_data: [";
36    for (int i = 0; i < send_data_count; i++)
37    {
38        std::cout << " " << send_data[i];
39    }
40    std::cout << " ]" << std::endl;
41
42    int recv_data_count = 10;
43    std::vector<double> recv_data(recv_data_count, 0);
44
45    // ノンブロッキングの send/recv を使って、相互にデータを送受信する
46    // 互いに同時に送信しても問題ない
47    MPI_Request requests[2];
48    MPI_Status statuses[2];
49
50    int other_rank = num_procs - 1 - my_rank;
51
52    MPI_Isend(
53        &send_data[0],
54        send_data_count,
55        MPI_DOUBLE,
56        other_rank, // 送り先のrank
57        0, // タグ。データをプログラムで区別するための番号。送信側と受信側で一致している必要がある。
58        MPI_COMM_WORLD,
59        &requests[0] // このノンブロッキング操作に対して発行されるIDをrequestsに受け取る
60    );
61    // この時点で2つのプロセスのそれぞれが送信しようとするが、
62    // まだどちらも受信しようとしていない。
63    // もしここでブロッキング関数をMPI_Sendを使っていたら、
64    // ここでどちらも送信が完了するまで MPI_Send 関数から帰ってこないで
65    // デッドロックになる。MPI_Isendならば、sendが完了しなくても関数から
66    // 帰ってくる。
67
68    MPI_Irecv(
69        &recv_data[0],
70        recv_data_count,
71        MPI_DOUBLE,
72        other_rank, // 送り元のrank
73        0, // タグ。送信側でつけた番号と同じもの
74        MPI_COMM_WORLD,
75        &requests[1] // このノンブロッキング操作に対して発行されるIDを記録する
76    );
77
78    // ノンブロッキング操作が完了するまで待つ。
79    // どの操作が完了するまで待つのかを、MPI_Request の配列で指定する。
80    // 操作ごとに失敗する可能性があるので、操作ごとの実行結果をMPI_Statusの
81    // 配列で受け取る
82
83    MPI_Waitall(
84        2, // 操作の数
85        &requests[0],
86        &statuses[0]
87    );
88
89    // 受信できたデータを表示する
90    std::cout << "rank: " << my_rank << ", recv_data: [";
91    for (int i = 0; i < recv_data_count; i++)
92    {
93        std::cout << " " << recv_data[i];
94    }
95    std::cout << " ]" << std::endl;
96
97    MPI_Finalize();
98    return EXIT_SUCCESS;
99}

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 ]