2.4.6. MPI_Scatterv
1#include <mpi.h>
2
3#include <iostream>
4#include <vector>
5#include <cstdlib>
6
7/*
8 * rank == 0 から各プロセスにまちまちの長さの配列データを送信する
9 * 受信側は事前に要素数が分かっていない。
10 *
11 * 実行方法: mpiexec -n 4 mpi_sample6
12 */
13
14// 後で定義する関数の前方宣言
15void make_send_data(int dest_rank, std::vector<double> &send_buffer,
16 std::vector<int> &data_count_array, std::vector<int> &send_data_displacements);
17
18int main(int argc, char *argv[])
19{
20 MPI_Init(&argc, &argv);
21
22 int num_procs;
23 int my_rank;
24
25 MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
26 MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
27
28 // 送信データバッファは全rankで定義するが、
29 // 中身をつめるのは root rank のみ
30 std::vector<double> send_data;
31 std::vector<int> data_counts;
32 std::vector<int> send_data_displacements;
33 // rank == 0 では全プロセス向けの送信データを作成する
34 if (my_rank == 0)
35 {
36 for (int k = 0; k < num_procs; k++)
37 {
38 make_send_data(k, send_data, data_counts, send_data_displacements);
39 }
40 // できたデータを表示する
41 std::cout << "rank: 0, send_data: [" << std::endl;
42 for (int k = 0; k < num_procs; k++)
43 {
44 for (int i = 0; i < data_counts[k]; i++)
45 {
46 int j = send_data_displacements[k] + i;
47 std::cout << " " << send_data[j];
48 }
49 std::cout << std::endl;
50 }
51 std::cout << " ]" << std::endl;
52 }
53 // 各rank向けのデータの個数を全rankに伝えるために
54 // MPI_Scatterを使う。「個数」という int 値を1要素ずつ
55 // 送ればよいので、受信バッファはスカラーのintにする
56 int recv_data_count;
57 MPI_Scatter(
58 &data_counts[0], // 送信バッファのアドレス。std::vectorの先頭要素のアドレス
59 1, // 各プロセスに送るデータ数。vectorの要素数ではない点に注意
60 MPI_INT, // 送信データの型
61 &recv_data_count, // 受信バッファのアドレス
62 1, // 受信するデータ数
63 MPI_INT, // 受信データの型
64 0, // 送信元rank
65 MPI_COMM_WORLD
66 );
67 // 全プロセスで受信予定のデータ数が分かったので、配列データの受信バッファを設ける
68 std::vector<double> recv_data(recv_data_count, 0);
69
70 MPI_Scatterv(
71 &send_data[0], // 送信データの先頭アドレス(rank == 0 でのみ利用される)
72 &data_counts[0], // 各rank向けに送り出す要素数が入った配列
73 &send_data_displacements[0], // 各rank向けのデータが配列のどの位置から始まるかが入った配列
74 MPI_DOUBLE, // 送信データの型
75 &recv_data[0], // 各rankでの受信データバッファ
76 recv_data_count, // 各rankでの受信データ数
77 MPI_DOUBLE, // 受信データの型
78 0, // 送信元とするrank
79 MPI_COMM_WORLD
80 );
81 // 各rankで受信できたデータを表示する
82 std::cout << "rank: " << my_rank << ", recv_data: [";
83 for (int i = 0; i < recv_data_count; i++)
84 {
85 std::cout << " " << recv_data[i];
86 }
87 std::cout << " ]" << std::endl;
88
89 MPI_Finalize();
90 return EXIT_SUCCESS;
91}
92
93void make_send_data(int dest_rank, std::vector<double> &send_data,
94 std::vector<int> &send_data_counts, std::vector<int> &send_data_displacements)
95{
96 int current_buffer_length = send_data.size();
97 send_data_displacements.push_back(current_buffer_length);
98 /*
99 * 5以上10未満の乱数で送信データ数を決める
100 */
101 int n = 5 + (rand() % 5);
102 send_data_counts.push_back(n);
103
104 /* 決めた個数のデータを送信バッファに入れる。*/
105 for (int i = 0; i < n; i++)
106 {
107 /* 値は 10 * rank + rankごとの通し番号 */
108 double val = dest_rank * 10 + i;
109 send_data.push_back(val);
110 }
111}
MPI_Scatterv は、MPI_Scatter と似て、配列データをルートから各rankに分配しますが、 分配の個数を宛先rankごとに独立に指定します。
送られてくるデータの個数が実行時に決まり、受信側では分からないような場合には、 データ本体を送るのに先立って、データの個数を送信側から受信側に伝える必要があります。 そのために、このサンプルでは一旦 MPI_Scatter を使って送信データ数だけを各rankに 伝達した後で、 MPI_Scatterv を使ってデータ本体を送っています。
各rankに送るデータの個数は、ルートrankにおいて乱数で決めています。
受信側では伝えられた個数に合わせて受信バッファの大きさを調整します。
openmpiによる実行例を示します。:
$ mpic++ -o mpi_sample6 mpi_sample6.cpp
$ mpiexec -n 4 mpi_sample6
rank: 0, send_data: [
0 1 2 3 4 5 6 7
10 11 12 13 14 15
20 21 22 23 24 25 26
30 31 32 33 34
]
rank: 0, recv_data: [ 0 1 2 3 4 5 6 7 ]
rank: 1, recv_data: [ 10 11 12 13 14 15 ]
rank: 2, recv_data: [ 20 21 22 23 24 25 26 ]
rank: 3, recv_data: [ 30 31 32 33 34 ]