2.3.7. MPI_Gatherv

  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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <mpi.h>

#include <iostream>
#include <vector>
#include <cstdlib>

// 後で定義する関数の前方宣言
void make_send_data(int my_rank, std::vector<double> &send_data);

/*
 * 各ノードで作成した、個数が事前には分かっていない配列を
 * 1つのノードに集める
 * 
 * 実行方法: mpiexec -n 4 mpi_sample7
 */
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);

    // rankごとに送信データを作成する
    // データの個数はプロセスごとに違う
    std::vector<double> send_data;
    make_send_data(my_rank, send_data);
    int send_data_count = send_data.size();

    // 各rankで作成したデータを表示する
    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;

    std::vector<int> data_counts;

    if (my_rank == 0)
    {
        data_counts.resize(num_procs);
    }

    // 各ランクで用意したデータの個数を rank == 0 に集める
    MPI_Gather(
        &send_data_count, // 送信データバッファ
        1, // プロセスごとの送信データ数
        MPI_INT, // データ型
        &data_counts[0], // 受信データバッファ。rank == 0 でのみ使われる
        1, // プロセスごとの受信データ数
        MPI_INT, // データ型
        0, // 受信するrank
        MPI_COMM_WORLD
    );

    // 受信データ総数を計算して、受信バッファを用意する
    int total_recv_count = 0;
    std::vector<int> recv_data_displacements;

    if (my_rank == 0)
    {
        for (int k = 0; k < num_procs; k++)
        {
            recv_data_displacements.push_back(total_recv_count);
            total_recv_count += data_counts[k];
        }
    }

    std::vector<double> recv_data;
    recv_data.resize(total_recv_count);

    MPI_Gatherv(
        &send_data[0],
        send_data_count,
        MPI_DOUBLE,
        &recv_data[0],
        &data_counts[0],
        &recv_data_displacements[0],
        MPI_DOUBLE,
        0,
        MPI_COMM_WORLD
    );

    if (my_rank == 0)
    {
        // 受信したデータを表示する
        std::cout << "rank: 0, recv_data: [" << std::endl;
        for (int k = 0; k < num_procs; k++)
        {
            for (int i = 0; i < data_counts[k]; i++)
            {
                int j = recv_data_displacements[k] + i;
                std::cout << " " << recv_data[j];
            }
            std::cout << std::endl;
        }
        std::cout << " ]" << std::endl;
    }

    MPI_Finalize();
    return EXIT_SUCCESS;
}

void make_send_data(int my_rank, std::vector<double> &send_data)
{
    srand((unsigned int) my_rank);
    /*
     * 5以上10未満の乱数
     */
    int n = 5 + (rand() % 5);
    for (int i = 0; i < n; i++)
    {
        double val = my_rank * 10 + i;
        send_data.push_back(val);
    }
}

Download the source code

MPI_Gatherv では、rankごとに個別に指定した個数の配列データを ルートrankに用意した全rank分の配列に格納します。

送信側と受信側の双方で転送するデータの個数を指定する必要が あるので、必要に応じて MPI_Gatherv の呼びたしに先立って 個数の情報を通信で伝達します。

この例では各rankで乱数によりデータの個数を決めており、その後で MPI_Gather を使って、全rankの個数情報をルートrankに集めています。 その後でMPI_Gatherv を使ってデータ本体を転送しています。

openmpiによる実行例を示します。:

$ mpic++ -o mpi_sample7 mpi_sample7.cpp
$ mpiexec -n 4 mpi_sample7
rank: 0, send_data: [ 0 1 2 3 4 5 6 7 ]
rank: 1, send_data: [ 10 11 12 13 14 15 16 17 ]
rank: 2, send_data: [ 20 21 22 23 24 ]
rank: 3, send_data: [ 30 31 32 33 34 35 ]
rank: 0, recv_data: [
 0 1 2 3 4 5 6 7
 10 11 12 13 14 15 16 17
 20 21 22 23 24
 30 31 32 33 34 35
 ]