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}
複数のプロセスの間で相互にデータを送り合いたい場合がありますが、 そのような場合に同期型の 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 ]