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}

Download the source code

自分で定義した構造体またはクラスにデータを保持させて、その構造体の配列を データとして送信したい場合があります。そのような時に便利なのが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}
]