MPI矩阵乘法m1函数结果异常:部分数组为空问题求助
MPI矩阵乘法代码中m1数组部分为空的问题排查与解决
问题重现
运行这段基于MPI的矩阵乘法代码时,发现m1的计算结果里,rank 0和最后一个rank之间的部分数组是空值(或垃圾值),多次调试后仍无法解决。
代码问题定位
1. 全局矩阵A、B未在所有进程同步
初始化A、B矩阵时,每个进程只负责初始化自己分配到的行(start_row到end_row),但后续拆分A11、A21等子矩阵时,需要访问整个A、B矩阵的所有行。其他进程没有这些行的数据,导致子矩阵中对应位置是未初始化的垃圾值,最终计算出的m1部分行无效。
2. 子矩阵拆分时的行范围错误
拆分A21、A22时,代码用了A[i+M][j],但i+M对应的行只有当该进程初始化过这些行时才有数据,否则是内存里的随机值。而每个进程只初始化了N/size行,大部分行的数据在当前进程中不存在。
3. multiplym1函数的栈溢出风险
函数里定义了result1[mySize][mySize]和result2[mySize][mySize],mySize是M=500,每个数组占500*500*4=1000000字节(约1MB),两个就是2MB,超出很多系统默认的栈大小,会导致栈溢出,破坏内存数据,进而出现异常结果。
4. MPI_Gatherv的接收缓冲区依赖错误
rank 0用本地的m1作为接收缓冲区,虽然MPI_Gatherv会覆盖对应位置,但前提是其他进程的m1计算正确。但前面的数据同步问题已经导致其他进程的m1数据错误,所以最终合并后的m1中间部分为空/无效。
修复方案
- 同步A、B矩阵到所有进程:初始化完成后,用
MPI_Allgatherv把每个进程的A、B行数据收集到所有进程,确保每个进程都有完整的A、B矩阵。 - 动态分配
multiplym1中的临时数组:避免栈溢出,改用malloc分配内存,使用完后释放。 - 确保子矩阵拆分时能访问完整数据:所有进程拥有完整A、B矩阵后,拆分子矩阵的逻辑可以正确获取所有行的数据。
- 统一分片逻辑:确保每个进程负责的行数计算准确,
MPI_Gatherv的counts和displs参数对应正确的偏移量。
完整修复代码
#include <stdlib.h> #include <time.h> #include <mpi.h> #define N 1000 #define M 1000 / 2 int A[N][N], B[N][N], C[N][N]; int m1[M][M], m2[M][M], m3[M][M], m4[M][M], m5[M][M], m6[M][M], m7[M][M]; int A11[M][M], A12[M][M], A21[M][M], A22[M][M], B11[M][M], B12[M][M], B21[M][M], B22[M][M]; int C11[M][M], C12[M][M], C21[M][M], C22[M][M]; int rank, size, start_row, end_row; void multiplym1(int mySize, int AA[M][M], int BB[M][M], int CC[M][M], int DD[M][M], int resfinal[M][M], int mystart_row, int myend_row) { // 动态分配临时数组,避免栈溢出 int **result1 = malloc(mySize * sizeof(int*)); int **result2 = malloc(mySize * sizeof(int*)); for (int i = 0; i < mySize; i++) { result1[i] = malloc(mySize * sizeof(int)); result2[i] = malloc(mySize * sizeof(int)); } for (int i = mystart_row; i < myend_row; i++) { for (int j = 0; j < mySize; j++) { result1[i][j] = AA[i][j] + BB[i][j]; result2[i][j] = CC[i][j] + DD[i][j]; } } for (int i = mystart_row; i < myend_row; i++) { for (int j = 0; j < mySize; j++) { resfinal[i][j] = 0; for (int k = 0; k < mySize; k++) { resfinal[i][j] += (result1[i][k] * result2[k][j]); } } } // 释放动态分配的内存 for (int i = 0; i < mySize; i++) { free(result1[i]); free(result2[i]); } free(result1); free(result2); } int main(int argc, char const *argv[]) { MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); // 每个进程用不同的随机种子,避免生成相同数据 srand(time(NULL) + rank); // 初始化A、B矩阵的分片行 int local_N_rows = N / size; if (rank == size - 1) { local_N_rows += N % size; } start_row = rank * (N / size); end_row = start_row + local_N_rows; // 初始化本地负责的行 for (int i = start_row; i < end_row; i++) { for (int j = 0; j < N; j++) { A[i][j] = rand() % 50; B[i][j] = rand() % 20; C[i][j] = 0; } } // 准备Allgatherv的参数,同步A矩阵到所有进程 int *counts_A = malloc(size * sizeof(int)); int *displs_A = malloc(size * sizeof(int)); int total = 0; for (int i = 0; i < size; i++) { counts_A[i] = (i == size - 1) ? (N / size + N % size) * N : (N / size) * N; displs_A[i] = total; total += counts_A[i]; } MPI_Allgatherv(&A[start_row][0], local_N_rows * N, MPI_INT, A, counts_A, displs_A, MPI_INT, MPI_COMM_WORLD); // 同步B矩阵到所有进程 int *counts_B = malloc(size * sizeof(int)); int *displs_B = malloc(size * sizeof(int)); total = 0; for (int i = 0; i < size; i++) { counts_B[i] = (i == size - 1) ? (N / size + N % size) * N : (N / size) * N; displs_B[i] = total; total += counts_B[i]; } MPI_Allgatherv(&B[start_row][0], local_N_rows * N, MPI_INT, B, counts_B, displs_B, MPI_INT, MPI_COMM_WORLD); // 拆分A、B为子矩阵,所有进程都有完整的A、B矩阵,遍历所有行初始化子矩阵 int local_M_rows = M / size; if (rank == size - 1) { local_M_rows += M % size; } start_row = rank * (M / size); end_row = start_row + local_M_rows; for (int i = 0; i < M; i++) { for (int j = 0; j < M; j++) { A11[i][j] = A[i][j]; A12[i][j] = A[i][j + M]; A21[i][j] = A[i + M][j]; A22[i][j] = A[i + M][j + M]; B11[i][j] = B[i][j]; B12[i][j] = B[i][j + M]; B21[i][j] = B[i + M][j]; B22[i][j] = B[i + M][j + M]; } } // 计算m1的本地部分 multiplym1(M, A11, A22, B11, B22, m1, start_row, end_row); // 准备Gatherv的参数,合并m1到rank 0 int *counts_m1 = malloc(size * sizeof(int)); int *displs_m1 = malloc(size * sizeof(int)); total = 0; for (int i = 0; i < size; i++) { counts_m1[i] = (i == size - 1) ? (M / size + M % size) * M : (M / size) * M; displs_m1[i] = total; total += counts_m1[i]; } MPI_Gatherv(&m1[start_row][0], local_M_rows * M, MPI_INT, m1, counts_m1, displs_m1, MPI_INT, 0, MPI_COMM_WORLD); if (rank == 0) { printf("合并后的m1部分结果:\n"); for (int i = 0; i < M; i += 49) { for (int j = 0; j < M; j += 100) { printf("m1[%d][%d] = %d\n", i, j, m1[i][j]); } } } // 释放内存 free(counts_A); free(displs_A); free(counts_B); free(displs_B); free(counts_m1); free(displs_m1); MPI_Finalize(); return 0; }
修复说明
- 用
MPI_Allgatherv同步A、B矩阵到所有进程,解决子矩阵拆分时的数据缺失问题。 - 把
multiplym1中的临时数组改为动态分配,避免栈溢出导致的内存错误。 - 给每个进程设置不同的随机种子,避免生成完全相同的矩阵数据。
- 统一分片计算逻辑,确保
MPI_Gatherv的参数准确对应每个进程负责的数据范围。
内容的提问来源于stack exchange,提问作者Mostafa Khastkhodaei
相关产品推荐
相关产品推荐

