MPI发送含任意大小向量的对象数组非连续部分的实现疑问
处理MPI中含可变大小vector的非连续对象数组发送问题
你需要发送MyClass数组的非连续部分,每个对象的vector<int> b大小不固定,当前逐个创建自定义类型发送的方式效率太低,以下是几个更优的实现方案:
方案一:使用MPI_Pack/MPI_Unpack打包发送
这是最适合新手的方案,无需创建大量自定义MPI类型,通过将所有待发送对象打包到一个缓冲区,一次性完成通信。
发送端步骤:
- 确定要发送的非连续对象列表(比如用索引数组指定要发送的元素下标)
- 计算总打包字节数:遍历每个待发送对象,累加
sizeof(int)(id) +3*sizeof(int)(a数组) +sizeof(int)(vector大小) +obj.b.size()*sizeof(int)(vector数据) - 分配足够大的缓冲区(比如用
vector<char>) - 逐个将对象的成员打包到缓冲区:
- 打包
id - 打包
a数组 - 打包
b.size() - 打包
b.data()指向的连续数据
- 打包
接收端步骤:
- 先接收待接收对象个数(可以先发送一个小的头信息)
- 接收总字节数,分配对应大小的缓冲区
- 接收整个缓冲区
- 逐个解包:
- 解包
id - 解包
a数组 - 解包
b的大小,resize目标对象的b - 解包数据到
b.data()
- 解包
代码示例:
发送端
// 假设要发送的非连续对象索引存在indices数组中,共count个 int count = 3; int indices[] = {0, 2, 4}; // 计算总打包大小 int total_size = 0; for (int i = 0; i < count; i++) { MyClass& obj = obj_arr[indices[i]]; total_size += sizeof(int) + 3*sizeof(int) + sizeof(int) + obj.b.size()*sizeof(int); } vector<char> send_buf(total_size); int position = 0; for (int i = 0; i < count; i++) { MyClass& obj = obj_arr[indices[i]]; // 打包id MPI_Pack(&obj.id, 1, MPI_INT, send_buf.data(), total_size, &position, MPI_COMM_WORLD); // 打包a数组 MPI_Pack(obj.a, 3, MPI_INT, send_buf.data(), total_size, &position, MPI_COMM_WORLD); // 打包vector大小 int b_size = obj.b.size(); MPI_Pack(&b_size, 1, MPI_INT, send_buf.data(), total_size, &position, MPI_COMM_WORLD); // 打包vector数据 MPI_Pack(obj.b.data(), b_size, MPI_INT, send_buf.data(), total_size, &position, MPI_COMM_WORLD); } // 先发送对象个数和总大小,再发送缓冲区 MPI_Send(&count, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); MPI_Send(&total_size, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); MPI_Send(send_buf.data(), total_size, MPI_PACKED, 1, 0, MPI_COMM_WORLD);
接收端
int count; MPI_Recv(&count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); int total_size; MPI_Recv(&total_size, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); vector<char> recv_buf(total_size); MPI_Recv(recv_buf.data(), total_size, MPI_PACKED, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); vector<MyClass> recv_objs(count); int position = 0; for (int i = 0; i < count; i++) { MyClass& obj = recv_objs[i]; // 解包id MPI_Unpack(recv_buf.data(), total_size, &position, &obj.id, 1, MPI_INT, MPI_COMM_WORLD); // 解包a数组 MPI_Unpack(recv_buf.data(), total_size, &position, obj.a, 3, MPI_INT, MPI_COMM_WORLD); // 解包vector大小 int b_size; MPI_Unpack(recv_buf.data(), total_size, &position, &b_size, 1, MPI_INT, MPI_COMM_WORLD); obj.b.resize(b_size); // 解包vector数据 MPI_Unpack(recv_buf.data(), total_size, &position, obj.b.data(), b_size, MPI_INT, MPI_COMM_WORLD); }
方案二:复用自定义类型 + MPI_Type_indexed
如果对通信性能要求较高,可以为每个不同vector大小的对象创建一个复用的自定义类型,然后用MPI_Type_indexed描述非连续的对象集合,一次性发送。
步骤:
- 遍历待发送对象,按
b.size()分组,为每个不同的size创建一个自定义MPI类型(和你之前创建单个对象类型的方法一致) - 收集每个待发送对象的:
- 对应的自定义类型
- 相对于数组起始地址的偏移量(
MPI_Aint_diff(MPI_Get_address(&obj), MPI_Get_address(obj_arr)))
- 使用
MPI_Type_indexed创建一个包含所有非连续元素的复合类型,每个元素对应自己的类型和偏移 - 提交类型后,一次性发送整个复合类型
注意点:
- 相同size的对象可以复用同一个MPI类型,减少类型创建的开销
- 所有自定义类型用完后要调用
MPI_Type_free释放资源
方案三:C++对象序列化
如果你的程序可以引入外部库,或者自己实现简单的序列化,可以将MyClass序列化为字节流,然后发送字节流。比如使用Boost.Serialization,或者自己写序列化函数将对象成员写入std::string或字节数组。
这种方法的优势是扩展性强,适合更复杂的对象结构,但需要处理跨平台的字节序问题(同构集群中可以忽略)。
方案对比
- MPI_Pack/Unpack:实现简单,无需复杂的类型管理,适合新手,通信次数少,效率优于逐个发送
- MPI_Type_indexed:性能最优,适合对延迟敏感的场景,但需要管理多个自定义类型,实现稍复杂
- 序列化:扩展性最强,适合复杂对象,但可能引入外部依赖,需要处理字节序
内容的提问来源于stack exchange,提问作者Chaithanya Kondur
相关产品推荐
相关产品推荐

