C++でOpenMPI入門6 一対一のブロッキング通信

並列化のより効率の良い制御をしようとすると, ブロードキャストではなく, プロセス間の一対一の通信が不可欠になる.

#include <iostream>
#include <mpi.h>

int main(int argc, char **argv)
{
    MPI::Init(argc, argv);

    int rank = MPI::COMM_WORLD.Get_rank();

    int send_value = rank;
    int recv_value = -1;

    int const TAG = 0;

    if (rank == 0)
    {
        MPI::COMM_WORLD.Send(&send_value, 1, MPI::INT, 1, TAG);
    }
    else if (rank == 1)
    {
        MPI::Status status;
        MPI::COMM_WORLD.Recv(&recv_value, 1, MPI::INT, 0, TAG, status);
        // MPI::COMM_WORLD.Recv(&recv_value, 1, MPI::INT, 0, TAG);                                                                                                                                                                                                                   
    }

    std::cout << "rank = " << rank
              << " : recv_value = " << recv_value << std::endl;

    MPI::Finalize();
}

この例では, ランク0からランク1へのsend_valueが送信され, ランク1ではrecv_valueに格納されている. それ以外では送受信は起きていないので, recv_valueは-1のままのはずである.

rank = 0 : recv_value = -1
rank = 1 : recv_value = 0
rank = 2 : recv_value = -1
rank = 3 : recv_value = -1

実際に受信を行っているランク1でのみ値が送り主のランクである0になっていることがわかる.

まあ, 見ての通りだが, Sendで送ってRecvで受け取る. 2番目の引数は送受信する数, 3番目は型名, 4番目は送信先・元のランク. 最後の引数はタグ. プロセス間で複数の情報をやりとりする場合, 今送って/受け取っている情報がどの情報なのかを区別するため, 同一のタグを指定する. そのため, タグの異なる情報は受け取らない.

ブロッキング通信ということの意味は, Sendした側にとっては相手が受け取ってくれるまで先に進まず, Recvする側にとっては相手が送ってきた情報を受けとるまでは先に進まないということである. 従って,

    if (rank == 0)
    {
        MPI::COMM_WORLD.Send(&send_value, 1, MPI::INT, 1, TAG);
        MPI::COMM_WORLD.Recv(&recv_value, 1, MPI::INT, 1, TAG);                                                                                                                                                                                                                   
    }
    else if (rank == 1)
    {
        MPI::COMM_WORLD.Send(&send_value, 1, MPI::INT, 0, TAG);
        MPI::COMM_WORLD.Recv(&recv_value, 1, MPI::INT, 0, TAG);                                                                                                                                                                                                                   
    }
}

とかすると, お互い相手が受信してくれるのを待ってしまうので, Sendから抜け出せなくなる. これをデッドロックと呼ぶ. これを避ける方法は単純にランク1側の送受信の順番をひっくり返せば良い(RecvしてからSend). もっと良い方法は, Sendrecvを使うことだ.

    if (rank == 0)
    {
        MPI::COMM_WORLD.Sendrecv(&send_value, 1, MPI::INT, 1, TAG,
                                 &recv_value, 1, MPI::INT, 1, TAG);
    }
    else if (rank == 1)
    {
        MPI::COMM_WORLD.Sendrecv(&send_value, 1, MPI::INT, 0, TAG,
                                 &recv_value, 1, MPI::INT, 0, TAG);
    }

これで0から1へと同時に, 1から0へもランクが送られ, recv_valueが更新される.

rank = 0 : recv_value = 1
rank = 1 : recv_value = 0
rank = 2 : recv_value = -1
rank = 3 : recv_value = -1

先ほどと異なり, ランク0プロセスでもrecv_valueが1に更新されている. ここでは, Sendrecvの引数として送受信先(4番目と9番目の引数)を同じランクとしているが, 別に同じである必要は無い. 詳しくは次回.