MPI 在 C++ 中发送带有向量 属性 的结构
MPI send struct with a vector property in C++
我想发送一个包含向量 属性 的结构。
typedef struct {
int id;
vector<int> neighbors;
} Node;
我知道我必须像 this answer 中那样创建一个 MPI 派生数据类型,但我不知道在我的情况下该怎么做,因为我在结构中有一个向量。
请注意,内部 vector<int>
看起来像这样:
struct vector {
size_t size;
size_t alloc_size;
int* data;
};
因此,如果您尝试按照 puelo 的建议发送结构,它不会访问向量下的实际数据,而是发送 size
字段、data
指针和任何数据在内存中跟在这些项之后,这很可能会导致无效的内存访问。向量中的实际数据不会像这样发送。
通常,MPI 不能很好地发送包含指向更多数据的指针的结构。相反,您应该尝试考虑如何发送实际的底层数据本身。
如果您能以连续的方式表示您的数据,MPI 通信将更加容易和高效。
您的 struct Node
看起来您正试图在图中表示一个节点。例如,您可以用邻接数组格式表示图形数据,其中所有邻居 ID 都表示在一个大向量中。把它想象成你之前 struct Node
中所有 neighbors
向量的串联。对于每个节点,您将把偏移量保存到新的 neighbors
向量中。
std::vector<int> node_ids(num_nodes);
std::vector<int> nodes_offsets(num_nodes);
std::vector<int> neighbors(num_edges);
// neighbors for node i are accessible via:
for (int j = node_offsets[i]; j <= node_offsets[i+1]-1; ++j) {
int neighbor = neighbors[j];
// ...
}
然后您可以使用 MPI 轻松 send/receive 此信息:
MPI_Send(&neighbors[0], MPI_INT, neighbors.size(), ...);
使用 MPI 时,为数据找到良好的数据布局是实现算法的最重要步骤之一。
如果你想保持高水平,发送对象,那么Boost.MPI是一个不错的选择。使用 Boost.MPI,您可以为结构指定高级序列化。
您不能(正确地)静态确定向量数据成员的偏移量。拼凑出一个有效的类型当然是可能的。但这也是搬起石头砸自己脚的好方法。您会在代码中引入假设(例如,矢量大小不会改变),一旦违反就会产生细微的错误。因此,在那种情况下,简单地在 MPI_Send
中分别发送 id
和 neighbours::data()
对我来说似乎更干净且更不容易出错 - 而不是使用不适合此用例的 MPI 类型.
我不喜欢为了做这件简单的事情而导入库的想法。所以这就是我所做的:
我认为没有理由让 MPI 了解对象的底层结构。所以我可以手动将它转换为缓冲区数组,因为接收方知道需要一个 Node 结构,所以可以在另一侧重新创建对象。所以最初我定义了一个 MPI_Contiguous
数据类型并发送它:
int size = (int) ((node.second.neighbors.size() + 1) * sizeof(int *));
MPI_Datatype datatype;
MPI_Type_contiguous(size, MPI_BYTE, &datatype);
MPI_Type_commit(&datatype);
MPI_Isend(&buffer, 1, datatype, proc_rank, TAG_DATA, MPI_COMM_WORLD, &request);
这是一个更通用的解决方案并且有效。
但是由于该结构包含一个 int
和一个 vector<int>
,我决定创建一个 int 缓冲区,其中第一个元素为 node.id
,重置为 node.neighbors
.另一方面,使用 MPI_Iprobe
(或同步 MPI_Probe
)和 MPI_Get_count
,我可以重新创建节点结构。这是代码:
int *seriealizeNode(Node node) {
//allocate buffer array
int *s = new int[node.neighbors.size() + 1];
//set the first element = Node.id
s[0] = node.id;
//set the rest elements to be the vector elements
for (int i = 0; i < node.neighbors.size(); ++i) {
s[i + 1] = node.neighbors[i];
}
return s;
}
Node deseriealizeNode(int buffer[], int size) {
Node node;
//get the Node.id
node.id = buffer[0];
//get the vector elements
for (int i = 1; i < size; ++i) {
node.neighbors.push_back(buffer[i]);
}
return node;
}
我认为必须有更多 efficient/faster 方法将 Node 转换为 int[] ,反之亦然。我想知道是否有人可以提供一些提示。
然后在发件人方面:
while (some_condition){
...
//if there is a pending request wait for it to finish and then free the buffer
if (request != MPI_REQUEST_NULL) {
MPI_Wait(&request, &status);
free(send_buffer);
}
// now send the node data
send_buffer = seriealizeNode(node.second);
int buffer_size = (int) (node.second.neighbors.size() + 1);
MPI_Isend(send_buffer, buffer_size, MPI_INT, proc, TAG_DATA, MPI_COMM_WORLD, &request);
...
}
在接收端:
int count = 0;
MPI_Iprobe(MPI_ANY_SOURCE, TAG_DATA, MPI_COMM_WORLD, &flag, &status);
if (flag) {
MPI_Get_count(&status, MPI_INT, &count);
int *s = new int[count];
MPI_Recv(s, count, MPI_INT, MPI_ANY_SOURCE, TAG_DATA, MPI_COMM_WORLD, &status);
Node node = deseriealizeNode(s, count);
free(s);
//my logic
}
现在它按预期工作了。
我想发送一个包含向量 属性 的结构。
typedef struct {
int id;
vector<int> neighbors;
} Node;
我知道我必须像 this answer 中那样创建一个 MPI 派生数据类型,但我不知道在我的情况下该怎么做,因为我在结构中有一个向量。
请注意,内部 vector<int>
看起来像这样:
struct vector {
size_t size;
size_t alloc_size;
int* data;
};
因此,如果您尝试按照 puelo 的建议发送结构,它不会访问向量下的实际数据,而是发送 size
字段、data
指针和任何数据在内存中跟在这些项之后,这很可能会导致无效的内存访问。向量中的实际数据不会像这样发送。
通常,MPI 不能很好地发送包含指向更多数据的指针的结构。相反,您应该尝试考虑如何发送实际的底层数据本身。
如果您能以连续的方式表示您的数据,MPI 通信将更加容易和高效。
您的 struct Node
看起来您正试图在图中表示一个节点。例如,您可以用邻接数组格式表示图形数据,其中所有邻居 ID 都表示在一个大向量中。把它想象成你之前 struct Node
中所有 neighbors
向量的串联。对于每个节点,您将把偏移量保存到新的 neighbors
向量中。
std::vector<int> node_ids(num_nodes);
std::vector<int> nodes_offsets(num_nodes);
std::vector<int> neighbors(num_edges);
// neighbors for node i are accessible via:
for (int j = node_offsets[i]; j <= node_offsets[i+1]-1; ++j) {
int neighbor = neighbors[j];
// ...
}
然后您可以使用 MPI 轻松 send/receive 此信息:
MPI_Send(&neighbors[0], MPI_INT, neighbors.size(), ...);
使用 MPI 时,为数据找到良好的数据布局是实现算法的最重要步骤之一。
如果你想保持高水平,发送对象,那么Boost.MPI是一个不错的选择。使用 Boost.MPI,您可以为结构指定高级序列化。
您不能(正确地)静态确定向量数据成员的偏移量。拼凑出一个有效的类型当然是可能的。但这也是搬起石头砸自己脚的好方法。您会在代码中引入假设(例如,矢量大小不会改变),一旦违反就会产生细微的错误。因此,在那种情况下,简单地在 MPI_Send
中分别发送 id
和 neighbours::data()
对我来说似乎更干净且更不容易出错 - 而不是使用不适合此用例的 MPI 类型.
我不喜欢为了做这件简单的事情而导入库的想法。所以这就是我所做的:
我认为没有理由让 MPI 了解对象的底层结构。所以我可以手动将它转换为缓冲区数组,因为接收方知道需要一个 Node 结构,所以可以在另一侧重新创建对象。所以最初我定义了一个 MPI_Contiguous
数据类型并发送它:
int size = (int) ((node.second.neighbors.size() + 1) * sizeof(int *));
MPI_Datatype datatype;
MPI_Type_contiguous(size, MPI_BYTE, &datatype);
MPI_Type_commit(&datatype);
MPI_Isend(&buffer, 1, datatype, proc_rank, TAG_DATA, MPI_COMM_WORLD, &request);
这是一个更通用的解决方案并且有效。
但是由于该结构包含一个 int
和一个 vector<int>
,我决定创建一个 int 缓冲区,其中第一个元素为 node.id
,重置为 node.neighbors
.另一方面,使用 MPI_Iprobe
(或同步 MPI_Probe
)和 MPI_Get_count
,我可以重新创建节点结构。这是代码:
int *seriealizeNode(Node node) {
//allocate buffer array
int *s = new int[node.neighbors.size() + 1];
//set the first element = Node.id
s[0] = node.id;
//set the rest elements to be the vector elements
for (int i = 0; i < node.neighbors.size(); ++i) {
s[i + 1] = node.neighbors[i];
}
return s;
}
Node deseriealizeNode(int buffer[], int size) {
Node node;
//get the Node.id
node.id = buffer[0];
//get the vector elements
for (int i = 1; i < size; ++i) {
node.neighbors.push_back(buffer[i]);
}
return node;
}
我认为必须有更多 efficient/faster 方法将 Node 转换为 int[] ,反之亦然。我想知道是否有人可以提供一些提示。
然后在发件人方面:
while (some_condition){
...
//if there is a pending request wait for it to finish and then free the buffer
if (request != MPI_REQUEST_NULL) {
MPI_Wait(&request, &status);
free(send_buffer);
}
// now send the node data
send_buffer = seriealizeNode(node.second);
int buffer_size = (int) (node.second.neighbors.size() + 1);
MPI_Isend(send_buffer, buffer_size, MPI_INT, proc, TAG_DATA, MPI_COMM_WORLD, &request);
...
}
在接收端:
int count = 0;
MPI_Iprobe(MPI_ANY_SOURCE, TAG_DATA, MPI_COMM_WORLD, &flag, &status);
if (flag) {
MPI_Get_count(&status, MPI_INT, &count);
int *s = new int[count];
MPI_Recv(s, count, MPI_INT, MPI_ANY_SOURCE, TAG_DATA, MPI_COMM_WORLD, &status);
Node node = deseriealizeNode(s, count);
free(s);
//my logic
}
现在它按预期工作了。