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