2.4.7. MPI_Gatherv

  1#include <mpi.h>
  2
  3#include <iostream>
  4#include <vector>
  5#include <cstdlib>
  6
  7// 後で定義する関数の前方宣言
  8void make_send_data(int my_rank, std::vector<double> &send_data);
  9
 10/*
 11 * 各ノードで作成した、個数が事前には分かっていない配列を
 12 * 1つのノードに集める
 13 * 
 14 * 実行方法: mpiexec -n 4 mpi_sample7
 15 */
 16int main(int argc, char *argv[])
 17{
 18    MPI_Init(&argc, &argv);
 19
 20    int num_procs;
 21    int my_rank;
 22
 23    MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
 24    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
 25
 26    // rankごとに送信データを作成する
 27    // データの個数はプロセスごとに違う
 28    std::vector<double> send_data;
 29    make_send_data(my_rank, send_data);
 30    int send_data_count = send_data.size();
 31
 32    // 各rankで作成したデータを表示する
 33    std::cout << "rank: " << my_rank << ", send_data: [";
 34    for (int i = 0; i < send_data_count; i++)
 35    {
 36        std::cout << " " << send_data[i];
 37    }
 38    std::cout << " ]" << std::endl;
 39
 40    std::vector<int> data_counts;
 41
 42    if (my_rank == 0)
 43    {
 44        data_counts.resize(num_procs);
 45    }
 46
 47    // 各ランクで用意したデータの個数を rank == 0 に集める
 48    MPI_Gather(
 49        &send_data_count, // 送信データバッファ
 50        1, // プロセスごとの送信データ数
 51        MPI_INT, // データ型
 52        &data_counts[0], // 受信データバッファ。rank == 0 でのみ使われる
 53        1, // プロセスごとの受信データ数
 54        MPI_INT, // データ型
 55        0, // 受信するrank
 56        MPI_COMM_WORLD
 57    );
 58
 59    // 受信データ総数を計算して、受信バッファを用意する
 60    int total_recv_count = 0;
 61    std::vector<int> recv_data_displacements;
 62
 63    if (my_rank == 0)
 64    {
 65        for (int k = 0; k < num_procs; k++)
 66        {
 67            recv_data_displacements.push_back(total_recv_count);
 68            total_recv_count += data_counts[k];
 69        }
 70    }
 71
 72    std::vector<double> recv_data;
 73    recv_data.resize(total_recv_count);
 74
 75    MPI_Gatherv(
 76        &send_data[0],
 77        send_data_count,
 78        MPI_DOUBLE,
 79        &recv_data[0],
 80        &data_counts[0],
 81        &recv_data_displacements[0],
 82        MPI_DOUBLE,
 83        0,
 84        MPI_COMM_WORLD
 85    );
 86
 87    if (my_rank == 0)
 88    {
 89        // 受信したデータを表示する
 90        std::cout << "rank: 0, recv_data: [" << std::endl;
 91        for (int k = 0; k < num_procs; k++)
 92        {
 93            for (int i = 0; i < data_counts[k]; i++)
 94            {
 95                int j = recv_data_displacements[k] + i;
 96                std::cout << " " << recv_data[j];
 97            }
 98            std::cout << std::endl;
 99        }
100        std::cout << " ]" << std::endl;
101    }
102
103    MPI_Finalize();
104    return EXIT_SUCCESS;
105}
106
107void make_send_data(int my_rank, std::vector<double> &send_data)
108{
109    srand((unsigned int) my_rank);
110    /*
111     * 5以上10未満の乱数
112     */
113    int n = 5 + (rand() % 5);
114    for (int i = 0; i < n; i++)
115    {
116        double val = my_rank * 10 + i;
117        send_data.push_back(val);
118    }
119}

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
 ]