MPI动态矩阵输入与非4×4矩阵乘法乱值问题求解
MPI矩阵乘法非4×4规模输出乱值问题修复方案
核心故障原因
程序输出乱值是C语言内存越界、MPI通信逻辑错误、计算逻辑不匹配三类问题叠加导致,和宏定义N本身的取值无关,具体问题点:
- 缓存数组长度不匹配:定义的接收缓存
aa、结果缓存cc长度仅为N,但MPI_Scatter/MPI_Gather接口指定的单进程数据块大小为N*N/size,当N*N/size > N时(比如N=10、进程数size=4时单块大小为25,远大于数组长度10),MPI读写缓存时会直接越界访问栈内存,写入随机垃圾值。 - 通信前无整除性校验:MPI_Scatter、MPI_Gather是固定大小等分数据的通信接口,要求总数据量拆分后每个进程拿到的数据块长度完全一致。原程序没有校验进程数和矩阵规模的匹配关系,比如N=5时总数据量25,启动2/3/4个进程时25无法被进程数整除,通信过程会直接读取非法内存地址。
- 冗余输入逻辑导致越界:rank0进程中通过
scanf读取row、col值用于初始化矩阵,但这两个值没有广播给其他进程,且如果输入的row/col不等于宏定义N,初始化矩阵时会越界写固定大小的a[N][N]、b[N][N]数组,直接破坏栈内存数据。 - 计算逻辑和数据拆分规则不匹配:原有计算循环默认每个进程仅拿到1行A矩阵的数据,但当N和进程数不满足
size=N的条件时,每个进程会分到多行A矩阵,原有循环仅计算1行结果,剩余分配给该进程的行完全没有计算,Gather回收的结果包含大量未初始化的垃圾值。 - 随机数范围不符合需求:
rand()%10生成的是09区间的整数,不符合19的初始化要求。
修复步骤
- 移除冗余的row、col手动输入逻辑,矩阵维度统一由宏定义N控制,避免输入值和数组大小不匹配导致的越界。
- 增加进程数整除性校验,按行拆分矩阵的场景下要求N能被启动的进程数整除,不满足条件时直接报错退出,避免非法内存访问。
- 调整缓存数组长度,每个进程的接收缓存
aa、结果缓存cc长度匹配单进程拿到的数据块大小,杜绝越界。 - 修正单进程矩阵乘法计算逻辑,按分配到的A矩阵行数逐行计算C矩阵对应行的结果。
- 调整随机数生成逻辑,生成1~9区间的整数。
- 修正并行计时逻辑,将原无意义的运行时间求和改为取所有进程的最长运行时间作为全局耗时,更符合并行程序的计时规则。
修复后完整代码
#define N 10 // 可任意修改为需要的矩阵阶数,启动进程时需保证进程数能整除N #include <stdio.h> #include <stdlib.h> #include <time.h> #include "mpi.h" void print_results(char *prompt, int a[N][N]); int main(int argc, char *argv[]) { int i, j, k, rank, size, sum = 0; int a[N][N]; int b[N][N]; int c[N][N]; int rows_per_proc, elements_per_proc; double time1, time2, duration, global; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &size); MPI_Comm_rank(MPI_COMM_WORLD, &rank); // 校验进程数和矩阵规模的匹配性 if (N % size != 0) { if (rank == 0) { printf("运行错误:矩阵阶数N=%d必须能被启动的进程数%d整除\n", N, size); } MPI_Finalize(); exit(1); } rows_per_proc = N / size; elements_per_proc = rows_per_proc * N; int aa[elements_per_proc]; int cc[elements_per_proc]; if(rank == 0){ srand(time(NULL)); // 初始化1~9区间的随机矩阵 for(i=0;i<N;i++) { for(j=0;j<N;j++){ a[i][j] = rand() % 9 + 1; } } for(i=0;i<N;i++){ for(j=0;j<N;j++){ b[i][j] = rand() % 9 + 1; } } } MPI_Barrier(MPI_COMM_WORLD); time1 = MPI_Wtime(); // 按行等分A矩阵分发给所有进程 MPI_Scatter(a, elements_per_proc, MPI_INT, aa, elements_per_proc, MPI_INT, 0, MPI_COMM_WORLD); // 广播完整B矩阵给所有进程 MPI_Bcast(b, N*N, MPI_INT, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD); // 逐行计算当前进程负责的C矩阵行结果 for (i = 0; i < rows_per_proc; i++) { for (j = 0; j < N; j++) { sum = 0; for (k = 0; k < N; k++) { sum += aa[i*N + k] * b[k][j]; } cc[i*N + j] = sum; } } // 收集所有进程的计算结果到rank0的C矩阵 MPI_Gather(cc, elements_per_proc, MPI_INT, c, elements_per_proc, MPI_INT, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD); time2 = MPI_Wtime(); duration = time2 - time1; MPI_Reduce(&duration, &global, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD); if(rank == 0) { printf("全局并行运行时间:%f 秒\n", global); } printf("进程%d 运行时间:%f 秒\n", rank, duration); if (rank == 0) { print_results("矩阵乘法结果 C = ", c); } MPI_Finalize(); return 0; } void print_results(char *prompt, int a[N][N]) { int i, j; printf ("\n\n%s\n", prompt); for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { printf(" %d", a[i][j]); } printf ("\n"); } printf ("\n\n"); }
运行说明
修改宏定义N为目标矩阵阶数后,启动MPI程序时指定的进程数必须是N的正约数:比如N=5时可以启动1/5个进程,N=10时可以启动1/2/5/10个进程,不满足匹配条件时程序会直接报错退出,不会输出乱值。
内容的提问来源于stack exchange,提问作者h3avyc0der
相关产品推荐
相关产品推荐

