2.4.11. MPI_Type_create_struct
1#include <mpi.h>
2
3#include <iostream>
4#include <iomanip>
5#include <vector>
6#include <cstdlib>
7#include <cmath>
8
9/*
10 * 自作のクラスをMPIにデータ型として登録して、
11 * クラスの配列を送受信する
12 *
13 */
14
15/*
16 * 船の座標と方位、速度を保持する構造体(全てがpublicなclass)
17 * BoatData には変数が 4 個あり、型は int, float[2], double, double
18 *
19 */
20 struct BoatData {
21 int boat_id_; // 船の個体番号
22 float pos_[2]; // 座標
23 double heading_; // 進行方向の方位
24 double vel_; // 速度
25};
26
27/*
28 * MPI に型を登録して得られたコードを保持する変数
29 */
30MPI_Datatype g_boat_data_type; // グローバル変数なので "g_"
31
32void register_type();
33void send_data();
34void recv_data();
35
36int main(int argc, char *argv[])
37{
38 MPI_Init(&argc, &argv);
39
40 int num_procs;
41 int my_rank;
42
43 MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
44 MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
45
46 if (num_procs != 2) {
47 std::cerr << "Run this program in MPI with 2 processes." << std::endl;
48 return EXIT_FAILURE;
49 }
50
51 register_type();
52
53 if (my_rank == 0)
54 {
55 send_data();
56 }
57 else
58 {
59 recv_data();
60 }
61
62 MPI_Finalize();
63 return EXIT_SUCCESS;
64}
65
66void register_type()
67{
68 // 各変数を配列と見た時の、それぞれの配列の長さ
69 int block_length[4] = { 1, 2, 1, 1};
70
71 // 構造体の先頭からの変位(バイト数)
72 // 長さの違う変数の間にはコンパイラが隙間を開ける(パディングという)ことがある
73 // ので、マクロを使って値を取得する
74 MPI_Aint displacements[4] = {
75 offsetof(BoatData, boat_id_),
76 offsetof(BoatData, pos_),
77 offsetof(BoatData, heading_),
78 offsetof(BoatData, vel_)
79 };
80
81 // 各変数の型
82 MPI_Datatype array_of_types[4] = {
83 MPI_INT,
84 MPI_FLOAT,
85 MPI_DOUBLE,
86 MPI_DOUBLE
87 };
88
89 // 以上のデータからstructの構造をMPIに登録して、コードを割り当ててもらう。
90 // このデータを送受信する全てのプロセスでそれぞれ登録する必要がある。
91
92 MPI_Type_create_struct(
93 4,
94 &block_length[0],
95 &displacements[0],
96 &array_of_types[0],
97 &g_boat_data_type
98 );
99
100 MPI_Type_commit(&g_boat_data_type);
101}
102
103/*
104 * rank 0 にて、rank 1 向けにデータを送信する
105 */
106void send_data()
107{
108 std::vector<BoatData> send_data;
109 send_data.resize(3);
110
111 // データ型の設定を間違えると変数の中身が一部しか送られなくなるので
112 // 確認のため、有効桁に非ゼロの値がたくさん入ったデータを送る
113 send_data[0].boat_id_ = 0;
114 send_data[0].pos_[0] = 1.010101010101010101; // pos は float
115 send_data[0].pos_[1] = 1.010101010101010101;
116 send_data[0].heading_ = 0.0/4.0 * M_PI;
117 send_data[0].vel_ = 1.010101010101010101; // vel は double
118
119 send_data[1].boat_id_ = 1;
120 send_data[1].pos_[0] = 2.020202020202020202;
121 send_data[1].pos_[1] = 2.020202020202020202;
122 send_data[1].heading_ = 1.0/4.0 * M_PI;
123 send_data[1].vel_ = 2.020202020202020202;
124
125 send_data[2].boat_id_ = 2;
126 send_data[2].pos_[0] = 3.030303030303030303;
127 send_data[2].pos_[1] = 3.030303030303030303;
128 send_data[2].heading_ = 2.0/4.0 * M_PI;
129 send_data[2].vel_ = 3.030303030303030303;
130
131 std::cout.precision(18); // double の有効桁数より少し多め
132 std::cout << "rank: 0, send_data: [" << std::endl;
133 for (size_t i = 0; i < send_data.size(); i++)
134 {
135 std::cout << "{ boat_id: " << send_data[i].boat_id_;
136 std::cout << ", pos: [ " << send_data[i].pos_[0] << " " << send_data[i].pos_[1] << " ]";
137 std::cout << ", heading: " << send_data[i].heading_;
138 std::cout << ", vel: " << send_data[i].vel_;
139 std::cout << "}" << std::endl;
140 }
141 std::cout << "]" << std::endl;
142
143 MPI_Send(
144 &send_data[0], // 送信データバッファ
145 send_data.size(), // 送信要素数
146 g_boat_data_type, // 要素の型コード
147 1, // 送信先のrank
148 1, // タグ。送り側と受け側で同じ値を指定する。値は自分で決める。
149 MPI_COMM_WORLD
150 );
151}
152
153/*
154 * rank 1 にて、rank 0 からデータを受信する。
155 */
156void recv_data()
157{
158 std::vector<BoatData> recv_data;
159 recv_data.resize(3);
160
161 MPI_Status status;
162 MPI_Recv(
163 &recv_data[0], // 受信データバッファ
164 recv_data.size(),
165 g_boat_data_type,
166 0, // 送信元のrank
167 1, // タグ
168 MPI_COMM_WORLD,
169 &status
170 );
171
172 std::cout.precision(18);
173 std::cout << "rank: 1, recv_data: [" << std::endl;
174 for (size_t i = 0; i < recv_data.size(); i++)
175 {
176 std::cout << "{ boat_id: " << recv_data[i].boat_id_;
177 std::cout << ", pos: [ " << recv_data[i].pos_[0] << " " << recv_data[i].pos_[1] << " ]";
178 std::cout << ", heading: " << recv_data[i].heading_;
179 std::cout << ", vel: " << recv_data[i].vel_;
180 std::cout << "}" << std::endl;
181 }
182 std::cout << "]" << std::endl;
183
184}
自分で定義した構造体またはクラスにデータを保持させて、その構造体の配列を データとして送信したい場合があります。そのような時に便利なのがMPIに 自作のデータ型を登録する機能です。
これを使うには、構造体のメンバ変数の登場順序、個数、型といった情報を MPIの関数に渡して型として登録します。登録すると型のコードを発行してもらう ことができ、MPIの各関数において、型コード(MPI_Type型)の値として指定する ことが可能になります。
このサンプルでは 2プロセスの間でデータをrank 0 から rank 1 に送信します。
openmpi を用いた実行例を示します。:
$ mpic++ -o mpi_sample11 mpi_sample11.cpp
$ mpiexec -n 2 mpi_sample11
rank: 0, send_data: [
{ boat_id: 0, pos: [ 1.01010096073150635 1.01010096073150635 ], heading: 0, vel: 1.01010101010101017}
{ boat_id: 1, pos: [ 2.0202019214630127 2.0202019214630127 ], heading: 0.785398163397448279, vel: 2.02020202020202033}
{ boat_id: 2, pos: [ 3.03030300140380859 3.03030300140380859 ], heading: 1.57079632679489656, vel: 3.03030303030303028}
]
rank: 1, recv_data: [
{ boat_id: 0, pos: [ 1.01010096073150635 1.01010096073150635 ], heading: 0, vel: 1.01010101010101017}
{ boat_id: 1, pos: [ 2.0202019214630127 2.0202019214630127 ], heading: 0.785398163397448279, vel: 2.02020202020202033}
{ boat_id: 2, pos: [ 3.03030300140380859 3.03030300140380859 ], heading: 1.57079632679489656, vel: 3.03030303030303028}
]