如何利用MPI将两个4x8子矩阵按行交错合并为8x8矩阵?
MPI合并交错排列的子矩阵
问题场景
有一个8x8的float*矩阵,计算后拆分为两个4x8子矩阵:
- 进程0持有行:
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 - 进程1持有行:
2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7
需要将两个子矩阵合并为完整矩阵,按行交错排列为:
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7
错误尝试代码
用户尝试使用MPI_Vector和MPI_Gatherv但失败,代码如下:
MPI_Datatype dt; int gsizes[2], distribs[2], dargs[2], psizes[2]; // original 4x8 submatrices gsizes[0] = 4; gsizes[1] = 8; distribs[0] = MPI_DISTRIBUTE_CYCLIC; distribs[1] = MPI_DISTRIBUTE_CYCLIC; // I want to interleave 2x8 blocks dargs[0] = 2; dargs[1] = 8; psizes[0] = 2; psizes[1] = 1; // I just want 1 proc to have all the row MPI_Type_vector(2, 2 * 8, 1, MPI_FLOAT, &dt); MPI_Type_create_resized(dt, 0, 2*8*sizeof(float), &dt); MPI_Type_commit(&dt); int displs[2] = {0, 0}; int recva[2] = {2, 2}; // I want to receive two 2*8 blocks from each process // src and result are two properly-allocated float* MPI_Gatherv(src, 4 * 8, MPI_FLOAT, result, recva, displs, dt, 0, group_comm);
执行结果仅收到64个浮点数中的17个,且仅来自进程1,顺序混乱。
问题分析
- 自定义类型错误:
MPI_Type_vector(2, 2*8, 1, MPI_FLOAT, &dt)的参数不符合内存布局需求,该定义会尝试从连续内存中取2个长度为16的块,步长为1,与子矩阵的实际存储逻辑不匹配。 - 位移参数错误:
displs[2] = {0, 0}导致两个进程的数据都写入接收缓冲区的起始位置,相互覆盖。 - 接收计数逻辑错误:
recva的设置未对应正确的块位置和数量。
可行解决方案
方案一:自定义MPI块类型 + MPI_Gatherv
通过定义2行8列的连续块类型,在根进程指定每个进程数据的接收位移,实现正确合并:
MPI_Datatype block_2x8; // 定义2行8列的连续块:每个块包含2行,每行8个float,块内连续 MPI_Type_contiguous(2 * 8, MPI_FLOAT, &block_2x8); MPI_Type_commit(&block_2x8); int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (rank == 0) { // 每个进程发送2个block_2x8类型的数据 int recvcounts[2] = {2, 2}; // 位移单位为自定义类型大小: // 进程0的两个块对应目标矩阵的0-1行、4-5行,位移为0、2 // 进程1的两个块对应目标矩阵的2-3行、6-7行,位移为1、3 int displs_int[2] = {0, 1}; MPI_Gatherv(src, 2, block_2x8, result, recvcounts, displs_int, block_2x8, 0, MPI_COMM_WORLD); } else { // 非根进程发送2个block_2x8类型的数据 MPI_Gatherv(src, 2, block_2x8, NULL, NULL, NULL, MPI_DATATYPE_NULL, 0, MPI_COMM_WORLD); } MPI_Type_free(&block_2x8);
方案二:直接拆分发送(直观易调试)
如果自定义类型容易出错,可直接将每个进程的4行拆分为两组2行,分别发送到根进程的对应位置:
int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (rank == 0) { // 复制自身数据到目标缓冲区对应位置 memcpy(result, src, 2*8*sizeof(float)); memcpy(result + 4*8, src + 2*8, 2*8*sizeof(float)); // 接收进程1的两组数据 MPI_Recv(result + 2*8, 2*8, MPI_FLOAT, 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(result + 6*8, 2*8, MPI_FLOAT, 1, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); } else if (rank == 1) { // 发送两组数据到根进程对应位置 MPI_Send(src, 2*8, MPI_FLOAT, 0, 0, MPI_COMM_WORLD); MPI_Send(src + 2*8, 2*8, MPI_FLOAT, 0, 1, MPI_COMM_WORLD); }
注意事项
- 确保
src和result缓冲区已正确分配:每个进程的src分配4*8*sizeof(float),根进程的result分配8*8*sizeof(float)。 - 使用自定义MPI类型后,需调用
MPI_Type_free释放类型资源。 - 方案二更适合小规模矩阵,调试维护简单;方案一更适合扩展到多进程或大矩阵场景。
内容的提问来源于stack exchange,提问作者G. Ianni
相关产品推荐
相关产品推荐

