MPI矩阵列向分解实现异常求助:自定义接收数据类型错误排查
问题描述
我有一个N=7、M=8的N×M矩阵,以行优先存储为一维数组:
[0, 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]
需要按列分配给3个进程,预期结果:
- 进程0:
[ 0, 1, 2, 8, 9, 10, 16, 17, 18, 24, 25, 26, 32, 33, 34, 40, 41, 42, 48, 49, 50]
- 进程1:
[ 3, 4, 5, 11, 12, 13, 19, 20, 21, 27, 28, 29, 35, 36, 37, 43, 44, 45, 51, 52, 53]
- 进程2:
[ 6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55]
每个进程通过以下代码计算本地参数:
int nloc = N; int mloc = rank < M%nproc ? (M/nproc)+1 : M/nproc; int *recv_vec = (int *)malloc(nloc*mloc*sizeof(int));
注:rank为进程编号(3个进程对应0、1、2)。
使用MPI_Scatterv实现分配,创建了自定义发送数据类型col_type:
MPI_Datatype col, col_type; MPI_Type_vector(nloc, 1, M, MPI_INT, &col); MPI_Type_commit(&col); //调整新创建的向量类型,使MPI_Scatterv知晓后续元素的正确偏移量 MPI_Type_create_resized(col, 0, sizeof(int), &col_type); MPI_Type_commit(&col_type);
初始调用MPI_Scatterv时,接收的数据是按列存储的一维数组,不符合行优先的预期格式。于是创建了自定义接收数据类型recv_row_type:
MPI_Datatype recv_row, recv_row_type; MPI_Type_vector(mloc,1,nloc,MPI_INT, &recv_row); MPI_Type_commit(&recv_row); MPI_Type_create_resized(recv_row, 0, sizeof(int), &recv_row_type); MPI_Type_commit(&recv_row_type);
修改后的MPI_Scatterv调用:
MPI_Scatterv(matrix, sendcounts, displs, col_type, recv_vec, nloc, recv_row_type, 0, MPI_COMM_WORLD);
但接收的数据顺序完全混乱,无法定位recv_row_type的问题,寻求技术帮助。
问题分析
问题出在接收数据类型的逻辑和MPI_Scatterv参数匹配上:
- 接收类型步长设置错误:
recv_row_type中MPI_Type_vector(mloc,1,nloc,MPI_INT, &recv_row)的步长参数错误。本地存储是nloc*mloc的一维数组,行优先存储下每行有mloc个元素,步长应该对应每行的元素数,而非nloc。 - 参数匹配逻辑混乱:发送端每个
col_type对应一列(nloc个元素),但接收端的count参数和自定义类型的对应关系不匹配,导致数据映射错位。
解决方案
推荐采用先接收列数据,再本地转置为行优先格式的方案,逻辑直观且不易出错:
- 保留发送端的
col_type,确保能正确选取矩阵的每一列。 - 用临时数组接收按列存储的数据,再通过转置操作将其转换为行优先格式。
修改后的完整代码
#define N 7 #define M 8 #include <stdio.h> #include <stdlib.h> #include <mpi.h> int main(int argc, char *argv[]){ int matrix[N*M]; int menum, nproc, i, j; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &menum); MPI_Comm_size(MPI_COMM_WORLD, &nproc); // 初始化矩阵 if (menum == 0) { for (i = 0; i < N; i++) { for (j = 0; j < M; j++) { matrix[(i*M)+j] = i * M + j; } } } // 计算本地参数并分配内存 int nloc = N; int mloc = menum < M%nproc ? (M/nproc)+1 : M/nproc; int *recv_vec = (int *)malloc(nloc*mloc*sizeof(int)); int *temp_vec = (int *)malloc(nloc*mloc*sizeof(int)); // 临时存储列数据 // 创建发送用的列类型 MPI_Datatype col, col_type; MPI_Type_vector(nloc, 1, M, MPI_INT, &col); MPI_Type_commit(&col); MPI_Type_create_resized(col, 0, sizeof(int), &col_type); MPI_Type_commit(&col_type); // 分发数据到各进程 int sendcounts[]={3,3,2}; int displs[]={0,3,6}; MPI_Scatterv(matrix, sendcounts, displs, col_type, temp_vec, mloc*nloc, MPI_INT, 0, MPI_COMM_WORLD); // 将列数据转置为行优先格式 for(i=0; i<nloc; i++){ for(j=0; j<mloc; j++){ recv_vec[i*mloc + j] = temp_vec[j*nloc + i]; } } free(temp_vec); // 打印接收结果 printf("进程 %d: \n", menum); for(i=0; i<nloc; i++){ for(j=0; j<mloc; j++){ printf("%3d ", recv_vec[i*mloc + j]); } printf("\n"); } printf("\n"); // 释放资源并终止MPI环境 free(recv_vec); MPI_Type_free(&col); MPI_Type_free(&col_type); MPI_Finalize(); return 0; }
代码解释
- 临时数组接收列数据:
temp_vec中存储的顺序是:第0列所有元素、第1列所有元素……第mloc-1列所有元素。 - 转置操作:通过双重循环将列存储的
temp_vec转换为行优先的recv_vec,核心逻辑为recv_vec[i*mloc + j] = temp_vec[j*nloc + i],其中i是行号,j是列号。 - 简化逻辑:删除了错误的接收自定义类型,避免了MPI类型匹配的复杂问题,同时保证结果符合预期。
内容的提问来源于stack exchange,提问作者Amato.g
相关产品推荐
相关产品推荐

