2.3.11. MPI_Type_create_struct

  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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include <mpi.h>

#include <iostream>
#include <iomanip>
#include <vector>
#include <cstdlib>
#include <cmath>

/*
 * 自作のクラスをMPIにデータ型として登録して、
 * クラスの配列を送受信する
 *
 */

/*
 * 船の座標と方位、速度を保持する構造体(全てがpublicなclass)
 * BoatData には変数が 4 個あり、型は int, float[2], double, double
 *
 */
 struct BoatData {
     int boat_id_; // 船の個体番号
     float pos_[2]; // 座標
     double heading_; // 進行方向の方位
     double vel_; // 速度
};

/*
 * MPI に型を登録して得られたコードを保持する変数
 */
MPI_Datatype g_boat_data_type; // グローバル変数なので "g_"

void register_type();
void send_data();
void recv_data();

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);

    if (num_procs != 2) {
        std::cerr << "Run this program in MPI with 2 processes." << std::endl;
        return EXIT_FAILURE;
    }

    register_type();

    if (my_rank == 0)
    {
        send_data();
    }
    else
    {
        recv_data();
    }

    MPI_Finalize();
    return EXIT_SUCCESS;
}

void register_type()
{
    // 各変数を配列と見た時の、それぞれの配列の長さ
    int block_length[4] = { 1, 2, 1, 1};

    // 構造体の先頭からの変位(バイト数)
    // 長さの違う変数の間にはコンパイラが隙間を開ける(パディングという)ことがある
    // ので、マクロを使って値を取得する
    MPI_Aint displacements[4] = {
        offsetof(BoatData, boat_id_),
        offsetof(BoatData, pos_),
        offsetof(BoatData, heading_),
        offsetof(BoatData, vel_)
    };

    // 各変数の型
    MPI_Datatype array_of_types[4] = {
        MPI_INT,
        MPI_FLOAT,
        MPI_DOUBLE,
        MPI_DOUBLE
    };

    // 以上のデータからstructの構造をMPIに登録して、コードを割り当ててもらう。
    // このデータを送受信する全てのプロセスでそれぞれ登録する必要がある。

    MPI_Type_create_struct(
        4,
        &block_length[0],
        &displacements[0],
        &array_of_types[0],
        &g_boat_data_type
    );

    MPI_Type_commit(&g_boat_data_type);
}

/*
 * rank 0 にて、rank 1 向けにデータを送信する
 */
void send_data()
{
    std::vector<BoatData> send_data;
    send_data.resize(3);

    // データ型の設定を間違えると変数の中身が一部しか送られなくなるので
    // 確認のため、有効桁に非ゼロの値がたくさん入ったデータを送る
    send_data[0].boat_id_ = 0;
    send_data[0].pos_[0] = 1.010101010101010101; // pos は float
    send_data[0].pos_[1] = 1.010101010101010101;
    send_data[0].heading_ = 0.0/4.0 * M_PI;
    send_data[0].vel_ =    1.010101010101010101; // vel は double

    send_data[1].boat_id_ = 1;
    send_data[1].pos_[0] = 2.020202020202020202;
    send_data[1].pos_[1] = 2.020202020202020202;
    send_data[1].heading_ = 1.0/4.0 * M_PI;
    send_data[1].vel_ =    2.020202020202020202;

    send_data[2].boat_id_ = 2;
    send_data[2].pos_[0] = 3.030303030303030303;
    send_data[2].pos_[1] = 3.030303030303030303;
    send_data[2].heading_ = 2.0/4.0 * M_PI;
    send_data[2].vel_ =    3.030303030303030303;

    std::cout.precision(18); // double の有効桁数より少し多め
    std::cout << "rank: 0, send_data: [" << std::endl;
    for (size_t i = 0; i < send_data.size(); i++)
    {
        std::cout << "{ boat_id: " << send_data[i].boat_id_;
        std::cout << ", pos: [ " << send_data[i].pos_[0] << " " << send_data[i].pos_[1] << " ]";
        std::cout << ", heading: " << send_data[i].heading_;
        std::cout << ", vel: " << send_data[i].vel_;
        std::cout << "}" << std::endl;
    }
    std::cout << "]" << std::endl;

    MPI_Send(
        &send_data[0], // 送信データバッファ
        send_data.size(), // 送信要素数
        g_boat_data_type, // 要素の型コード
        1, // 送信先のrank
        1, // タグ。送り側と受け側で同じ値を指定する。値は自分で決める。
        MPI_COMM_WORLD
    );
}

/*
 * rank 1 にて、rank 0 からデータを受信する。
 */
void recv_data()
{
    std::vector<BoatData> recv_data;
    recv_data.resize(3);

    MPI_Status status;
    MPI_Recv(
        &recv_data[0], // 受信データバッファ
        recv_data.size(),
        g_boat_data_type,
        0, // 送信元のrank
        1, // タグ
        MPI_COMM_WORLD,
        &status
    );

    std::cout.precision(18);
    std::cout << "rank: 1, recv_data: [" << std::endl;
    for (size_t i = 0; i < recv_data.size(); i++)
    {
        std::cout << "{ boat_id: " << recv_data[i].boat_id_;
        std::cout << ", pos: [ " << recv_data[i].pos_[0] << " " << recv_data[i].pos_[1] << " ]";
        std::cout << ", heading: " << recv_data[i].heading_;
        std::cout << ", vel: " << recv_data[i].vel_;
        std::cout << "}" << std::endl;
    }
    std::cout << "]" << std::endl;

}

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}
]