如何使用 MPI_Send 在 MPI 中发送嵌套结构
How to send nested structures in MPI using MPI_Send
我需要使用 MPI_SEND 发送 plista 数组:
Traza* plista;
struct Evento{
char* evento;
unsigned char cant;
};
struct Traza
{
char* nombre;
Evento* eventos;
unsigned int cantEventos;
bool revisado;
unsigned int idTraza;
};
我在人民使用MPI_PACKED中阅读了一些文章,但这是一个复杂的结构。
您要发送的数据结构很复杂,因为它包含指向子结构的指针。传达指针显然没有意义,相反,您必须以深拷贝的方式求助于子结构。此外,一些子结构包含动态长度的数组,只有发送方事先知道,因此接收方无法分配正确的内存量。
您可能想创建自己的复合 MPI 数据类型,实际上可以通过使用 MPI_BOTTOM
并对数据的所有子部分的绝对地址进行编码来实现结构体。每次要进行通信时都需要重新创建此自定义数据类型,因为您可能会使用不同的数据,因此绝对地址会发生变化。但是,这仅适用于发送方,因为在接收方您仍然不知道为数据结构的每个部分分配多少内存。
解决这个问题的一种方法是进行多次通信,先发送大小,然后分别发送数据。然而,这有增加通信数量的缺点,并且会引入不必要的延迟。
输入您已经提到的 MPI_PACKED
。 MPI_PACKED
允许您一次将要发送的数据打包到一个缓冲区中,然后在一次通信中发送整个缓冲区。在接收端,可以单次通信接收,然后逐个解包。
下面是使用 MPI_PACKED
.
的数据结构的解决方案
注意:我对拉丁语言几乎一无所知,所以我不得不猜测你的一些标识符的意思。即我解读
cant
作为某种元素计数
Evento::evento
作为恰好适合 char
的数字数组,数组的长度在 Evento::cant
. 中
Traza::nombre
作为数字数组,Traza::eventos
作为事件数组,两个数组的长度由 Traza::cantEventos
给出
纠正这些不正确的解释应该很容易(只需从正确的来源获取数组的大小,然后按照我在消息中打包 Traza
的数量的方式将它们单独打包).
#include <iomanip>
#include <iostream>
#include <ostream>
#include <vector>
#include <mpi.h>
struct Evento {
char* evento;
unsigned char cant;
};
struct Traza
{
char* nombre;
Evento* eventos;
unsigned int cantEventos;
bool revisado;
unsigned int idTraza;
};
void pack_plista(int incount, Traza* data, MPI_Comm comm,
std::vector<char> &buf)
{
int pos = 0;
buf.clear();
int size;
MPI_Pack_size(1, MPI_INT, comm, &size);
buf.resize(pos+size);
MPI_Pack(&incount, 1, MPI_INT, buf.data(), buf.size(), &pos, comm);
for(int t = 0; t < incount; ++t)
{
MPI_Pack_size(2, MPI_UNSIGNED, comm, &size);
buf.resize(pos+size);
MPI_Pack(&data[t].cantEventos, 1, MPI_UNSIGNED,
buf.data(), buf.size(), &pos, comm);
MPI_Pack(&data[t].idTraza, 1, MPI_UNSIGNED,
buf.data(), buf.size(), &pos, comm);
MPI_Pack_size(1, MPI_UNSIGNED_CHAR, comm, &size);
buf.resize(pos+size);
{ // MPI does not know about C++ bool
unsigned char revisado = data[t].revisado;
MPI_Pack(&revisado, 1, MPI_UNSIGNED_CHAR,
buf.data(), buf.size(), &pos, comm);
}
// This interprets Traza::nombre as a character code, which is probably incorrect
// However, that is unlikely to be a problem, unless you are in a
// heterogeneous ASCII/EBCDIC environment
MPI_Pack_size(data[t].cantEventos, MPI_CHAR, comm, &size);
buf.resize(pos+size);
MPI_Pack(data[t].nombre, data[t].cantEventos, MPI_CHAR,
buf.data(), buf.size(), &pos, comm);
for(unsigned int e = 0; e < data[t].cantEventos; ++e)
{
// send count (interpret as a number)
MPI_Pack_size(1, MPI_UNSIGNED_CHAR, comm, &size);
buf.resize(pos+size);
MPI_Pack(&data[t].eventos[e].cant, 1, MPI_UNSIGNED_CHAR,
buf.data(), buf.size(), &pos, comm);
// send events (interpret as character codes)
MPI_Pack_size(data[t].eventos[e].cant, MPI_CHAR, comm, &size);
buf.resize(pos+size);
MPI_Pack(data[t].eventos[e].evento, data[t].eventos[e].cant, MPI_CHAR,
buf.data(), buf.size(), &pos, comm);
}
}
buf.resize(pos);
}
void unpack_plista(int &outcount, Traza* &data, MPI_Comm comm,
// buf cannot be reference-to-const since MPI_Unpack takes
// pointer-to-nonconst
std::vector<char> &buf)
{
int pos = 0;
MPI_Unpack(buf.data(), buf.size(), &pos, &outcount, 1, MPI_INT, comm);
data = new Traza[outcount];
for(int t = 0; t < outcount; ++t)
{
MPI_Unpack(buf.data(), buf.size(), &pos,
&data[t].cantEventos, 1, MPI_UNSIGNED, comm);
MPI_Unpack(buf.data(), buf.size(), &pos,
&data[t].idTraza, 1, MPI_UNSIGNED, comm);
{ // MPI does not know about C++ bool
unsigned char revisado;
MPI_Unpack(buf.data(), buf.size(), &pos,
&revisado, 1, MPI_UNSIGNED_CHAR, comm);
data[t].revisado = revisado;
}
// This interprets Traza::nombre as a character code, which is probably incorrect
// However, that is unlikely to be a problem, unless you are in a
// heterogeneous ASCII/EBCDIC environment
data[t].nombre = new char[data[t].cantEventos];
MPI_Unpack(buf.data(), buf.size(), &pos,
data[t].nombre, data[t].cantEventos, MPI_CHAR, comm);
data[t].eventos = new Evento[data[t].cantEventos];
for(unsigned int e = 0; e < data[t].cantEventos; ++e)
{
// receive count (interpret as a number)
MPI_Unpack(buf.data(), buf.size(), &pos,
&data[t].eventos[e].cant, 1, MPI_UNSIGNED_CHAR, comm);
// receive events (interpret as character codes)
data[t].eventos[e].evento = new char[data[t].eventos[e].cant];
MPI_Unpack(buf.data(), buf.size(), &pos,
data[t].eventos[e].evento, data[t].eventos[e].cant, MPI_CHAR,
comm);
}
}
}
void send_plista(int incount, Traza* data, int dest, int tag, MPI_Comm comm)
{
std::vector<char> buf;
pack_plista(incount, data, comm, buf);
MPI_Send(buf.data(), buf.size(), MPI_PACKED, dest, tag, comm);
}
void recv_plista(int &outcount, Traza* &data,
int src, int tag, MPI_Comm comm)
{
MPI_Status status;
MPI_Probe(src, tag, comm, &status);
int size;
MPI_Get_count(&status, MPI_PACKED, &size);
std::vector<char> buf(size);
MPI_Recv(buf.data(), buf.size(), MPI_PACKED, src, tag, comm, &status);
unpack_plista(outcount, data, comm, buf);
}
void make_test_data(int &count, Traza *&data) {
count = 2;
data = new Traza[2] {
{
new char[3] { char(0), char(1), char(3) },
new Evento[3] {
{
new char[4] { 'a', 'b', 'c', 'd' },
(unsigned char)4,
},
{
new char[3] { 'e', 'f', 'g' },
(unsigned char)3,
},
{
new char[2] { 'h', 'i' },
(unsigned char)2,
},
},
3u,
true,
0u,
},
{
new char[1] { char(4) },
new Evento[1] {
{
new char[1] { 'j' },
(unsigned char)1,
},
},
1u,
false,
1u,
},
};
}
void print_data(std::ostream &out, int count, const Traza *data)
{
for(int t = 0; t < count; ++t)
{
std::cout << "{\n"
<< " nombre = { ";
for(unsigned e = 0; e < data[t].cantEventos; ++e)
std::cout << int(data[t].nombre[e]) << ", ";
std::cout << "},\n"
<< " eventos = {\n";
for(unsigned e = 0; e < data[t].cantEventos; ++e)
{
std::cout << " {\n"
<< " evento = { ";
for(int c = 0; c < data[t].eventos[e].cant; ++c)
std::cout << "'" << data[t].eventos[e].evento[c] << "', ";
std::cout << "},\n"
<< " cant = " << int(data[t].eventos[e].cant) << ",\n"
<< " },\n";
}
std::cout << " },\n"
<< " cantEventos = " << data[t].cantEventos << ",\n"
<< " revisado = " << data[t].revisado << ",\n"
<< " idTraza = " << data[t].idTraza << ",\n"
<< "}," << std::endl;
}
}
int main(int argc, char **argv)
{
int rank;
Traza *plista = nullptr;
int count = 0;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(rank == 1) {
make_test_data(count, plista);
send_plista(count, plista, 0, 0, MPI_COMM_WORLD);
}
else if(rank == 0) {
recv_plista(count, plista, 1, 0, MPI_COMM_WORLD);
std::cout << std::boolalpha;
print_data(std::cout, count, plista);
}
// don't bother freeing allocated memory, the process is ending anyway
MPI_Finalize();
}
样本运行:
-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Wed Sep 9 20:04:24
set -ex; mpic++ -std=c++11 -Wall -Wno-literal-suffix -o check check.cc; mpirun -n 2 ./check
+ mpic++ -std=c++11 -Wall -Wno-literal-suffix -o check check.cc
+ mpirun -n 2 ./check
{
nombre = { 0, 1, 3, },
eventos = {
{
evento = { 'a', 'b', 'c', 'd', },
cant = 4,
},
{
evento = { 'e', 'f', 'g', },
cant = 3,
},
{
evento = { 'h', 'i', },
cant = 2,
},
},
cantEventos = 3,
revisado = true,
idTraza = 0,
},
{
nombre = { 4, },
eventos = {
{
evento = { 'j', },
cant = 1,
},
},
cantEventos = 1,
revisado = false,
idTraza = 1,
},
Compilation finished at Wed Sep 9 20:04:26
我需要使用 MPI_SEND 发送 plista 数组:
Traza* plista;
struct Evento{
char* evento;
unsigned char cant;
};
struct Traza
{
char* nombre;
Evento* eventos;
unsigned int cantEventos;
bool revisado;
unsigned int idTraza;
};
我在人民使用MPI_PACKED中阅读了一些文章,但这是一个复杂的结构。
您要发送的数据结构很复杂,因为它包含指向子结构的指针。传达指针显然没有意义,相反,您必须以深拷贝的方式求助于子结构。此外,一些子结构包含动态长度的数组,只有发送方事先知道,因此接收方无法分配正确的内存量。
您可能想创建自己的复合 MPI 数据类型,实际上可以通过使用 MPI_BOTTOM
并对数据的所有子部分的绝对地址进行编码来实现结构体。每次要进行通信时都需要重新创建此自定义数据类型,因为您可能会使用不同的数据,因此绝对地址会发生变化。但是,这仅适用于发送方,因为在接收方您仍然不知道为数据结构的每个部分分配多少内存。
解决这个问题的一种方法是进行多次通信,先发送大小,然后分别发送数据。然而,这有增加通信数量的缺点,并且会引入不必要的延迟。
输入您已经提到的 MPI_PACKED
。 MPI_PACKED
允许您一次将要发送的数据打包到一个缓冲区中,然后在一次通信中发送整个缓冲区。在接收端,可以单次通信接收,然后逐个解包。
下面是使用 MPI_PACKED
.
注意:我对拉丁语言几乎一无所知,所以我不得不猜测你的一些标识符的意思。即我解读
cant
作为某种元素计数Evento::evento
作为恰好适合char
的数字数组,数组的长度在Evento::cant
. 中
Traza::nombre
作为数字数组,Traza::eventos
作为事件数组,两个数组的长度由Traza::cantEventos
给出
纠正这些不正确的解释应该很容易(只需从正确的来源获取数组的大小,然后按照我在消息中打包 Traza
的数量的方式将它们单独打包).
#include <iomanip>
#include <iostream>
#include <ostream>
#include <vector>
#include <mpi.h>
struct Evento {
char* evento;
unsigned char cant;
};
struct Traza
{
char* nombre;
Evento* eventos;
unsigned int cantEventos;
bool revisado;
unsigned int idTraza;
};
void pack_plista(int incount, Traza* data, MPI_Comm comm,
std::vector<char> &buf)
{
int pos = 0;
buf.clear();
int size;
MPI_Pack_size(1, MPI_INT, comm, &size);
buf.resize(pos+size);
MPI_Pack(&incount, 1, MPI_INT, buf.data(), buf.size(), &pos, comm);
for(int t = 0; t < incount; ++t)
{
MPI_Pack_size(2, MPI_UNSIGNED, comm, &size);
buf.resize(pos+size);
MPI_Pack(&data[t].cantEventos, 1, MPI_UNSIGNED,
buf.data(), buf.size(), &pos, comm);
MPI_Pack(&data[t].idTraza, 1, MPI_UNSIGNED,
buf.data(), buf.size(), &pos, comm);
MPI_Pack_size(1, MPI_UNSIGNED_CHAR, comm, &size);
buf.resize(pos+size);
{ // MPI does not know about C++ bool
unsigned char revisado = data[t].revisado;
MPI_Pack(&revisado, 1, MPI_UNSIGNED_CHAR,
buf.data(), buf.size(), &pos, comm);
}
// This interprets Traza::nombre as a character code, which is probably incorrect
// However, that is unlikely to be a problem, unless you are in a
// heterogeneous ASCII/EBCDIC environment
MPI_Pack_size(data[t].cantEventos, MPI_CHAR, comm, &size);
buf.resize(pos+size);
MPI_Pack(data[t].nombre, data[t].cantEventos, MPI_CHAR,
buf.data(), buf.size(), &pos, comm);
for(unsigned int e = 0; e < data[t].cantEventos; ++e)
{
// send count (interpret as a number)
MPI_Pack_size(1, MPI_UNSIGNED_CHAR, comm, &size);
buf.resize(pos+size);
MPI_Pack(&data[t].eventos[e].cant, 1, MPI_UNSIGNED_CHAR,
buf.data(), buf.size(), &pos, comm);
// send events (interpret as character codes)
MPI_Pack_size(data[t].eventos[e].cant, MPI_CHAR, comm, &size);
buf.resize(pos+size);
MPI_Pack(data[t].eventos[e].evento, data[t].eventos[e].cant, MPI_CHAR,
buf.data(), buf.size(), &pos, comm);
}
}
buf.resize(pos);
}
void unpack_plista(int &outcount, Traza* &data, MPI_Comm comm,
// buf cannot be reference-to-const since MPI_Unpack takes
// pointer-to-nonconst
std::vector<char> &buf)
{
int pos = 0;
MPI_Unpack(buf.data(), buf.size(), &pos, &outcount, 1, MPI_INT, comm);
data = new Traza[outcount];
for(int t = 0; t < outcount; ++t)
{
MPI_Unpack(buf.data(), buf.size(), &pos,
&data[t].cantEventos, 1, MPI_UNSIGNED, comm);
MPI_Unpack(buf.data(), buf.size(), &pos,
&data[t].idTraza, 1, MPI_UNSIGNED, comm);
{ // MPI does not know about C++ bool
unsigned char revisado;
MPI_Unpack(buf.data(), buf.size(), &pos,
&revisado, 1, MPI_UNSIGNED_CHAR, comm);
data[t].revisado = revisado;
}
// This interprets Traza::nombre as a character code, which is probably incorrect
// However, that is unlikely to be a problem, unless you are in a
// heterogeneous ASCII/EBCDIC environment
data[t].nombre = new char[data[t].cantEventos];
MPI_Unpack(buf.data(), buf.size(), &pos,
data[t].nombre, data[t].cantEventos, MPI_CHAR, comm);
data[t].eventos = new Evento[data[t].cantEventos];
for(unsigned int e = 0; e < data[t].cantEventos; ++e)
{
// receive count (interpret as a number)
MPI_Unpack(buf.data(), buf.size(), &pos,
&data[t].eventos[e].cant, 1, MPI_UNSIGNED_CHAR, comm);
// receive events (interpret as character codes)
data[t].eventos[e].evento = new char[data[t].eventos[e].cant];
MPI_Unpack(buf.data(), buf.size(), &pos,
data[t].eventos[e].evento, data[t].eventos[e].cant, MPI_CHAR,
comm);
}
}
}
void send_plista(int incount, Traza* data, int dest, int tag, MPI_Comm comm)
{
std::vector<char> buf;
pack_plista(incount, data, comm, buf);
MPI_Send(buf.data(), buf.size(), MPI_PACKED, dest, tag, comm);
}
void recv_plista(int &outcount, Traza* &data,
int src, int tag, MPI_Comm comm)
{
MPI_Status status;
MPI_Probe(src, tag, comm, &status);
int size;
MPI_Get_count(&status, MPI_PACKED, &size);
std::vector<char> buf(size);
MPI_Recv(buf.data(), buf.size(), MPI_PACKED, src, tag, comm, &status);
unpack_plista(outcount, data, comm, buf);
}
void make_test_data(int &count, Traza *&data) {
count = 2;
data = new Traza[2] {
{
new char[3] { char(0), char(1), char(3) },
new Evento[3] {
{
new char[4] { 'a', 'b', 'c', 'd' },
(unsigned char)4,
},
{
new char[3] { 'e', 'f', 'g' },
(unsigned char)3,
},
{
new char[2] { 'h', 'i' },
(unsigned char)2,
},
},
3u,
true,
0u,
},
{
new char[1] { char(4) },
new Evento[1] {
{
new char[1] { 'j' },
(unsigned char)1,
},
},
1u,
false,
1u,
},
};
}
void print_data(std::ostream &out, int count, const Traza *data)
{
for(int t = 0; t < count; ++t)
{
std::cout << "{\n"
<< " nombre = { ";
for(unsigned e = 0; e < data[t].cantEventos; ++e)
std::cout << int(data[t].nombre[e]) << ", ";
std::cout << "},\n"
<< " eventos = {\n";
for(unsigned e = 0; e < data[t].cantEventos; ++e)
{
std::cout << " {\n"
<< " evento = { ";
for(int c = 0; c < data[t].eventos[e].cant; ++c)
std::cout << "'" << data[t].eventos[e].evento[c] << "', ";
std::cout << "},\n"
<< " cant = " << int(data[t].eventos[e].cant) << ",\n"
<< " },\n";
}
std::cout << " },\n"
<< " cantEventos = " << data[t].cantEventos << ",\n"
<< " revisado = " << data[t].revisado << ",\n"
<< " idTraza = " << data[t].idTraza << ",\n"
<< "}," << std::endl;
}
}
int main(int argc, char **argv)
{
int rank;
Traza *plista = nullptr;
int count = 0;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(rank == 1) {
make_test_data(count, plista);
send_plista(count, plista, 0, 0, MPI_COMM_WORLD);
}
else if(rank == 0) {
recv_plista(count, plista, 1, 0, MPI_COMM_WORLD);
std::cout << std::boolalpha;
print_data(std::cout, count, plista);
}
// don't bother freeing allocated memory, the process is ending anyway
MPI_Finalize();
}
样本运行:
-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Wed Sep 9 20:04:24
set -ex; mpic++ -std=c++11 -Wall -Wno-literal-suffix -o check check.cc; mpirun -n 2 ./check
+ mpic++ -std=c++11 -Wall -Wno-literal-suffix -o check check.cc
+ mpirun -n 2 ./check
{
nombre = { 0, 1, 3, },
eventos = {
{
evento = { 'a', 'b', 'c', 'd', },
cant = 4,
},
{
evento = { 'e', 'f', 'g', },
cant = 3,
},
{
evento = { 'h', 'i', },
cant = 2,
},
},
cantEventos = 3,
revisado = true,
idTraza = 0,
},
{
nombre = { 4, },
eventos = {
{
evento = { 'j', },
cant = 1,
},
},
cantEventos = 1,
revisado = false,
idTraza = 1,
},
Compilation finished at Wed Sep 9 20:04:26