MPI Reduce计算数组部分和总和失效问题排查
原代码的核心错误
你的MPI_Reduce未正确工作,主要是这几个问题:
MPI_Reduce的count参数错误
你写的是MPI_Reduce(&partial_sum,&sum, n, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);,第三个参数n是数组总长度,但每个进程要归约的是单个整数(partial_sum),所以这里应该传1,不是n。传n会导致MPI尝试读取每个进程中从partial_sum开始的n个整数,超出内存范围后结果自然错误。主进程的partial_sum未赋值
主进程(pid=0)自己计算了部分和但存在sum里,而MPI_Reduce是把所有进程的partial_sum汇总到sum中。主进程的partial_sum初始为0,没把自己的部分和放进去,会导致归约时丢失主进程的计算贡献。打印时机错误
主进程在MPI_Reduce执行前就打印了sum,这时候还没完成归约,打印的只是自己单独计算的部分和,自然只有前半段的结果。必须等MPI_Reduce执行完再打印。
修正后的代码
// size of array #define n 10 int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // Temporary array for other process int a2[1000]; int main(int argc, char* argv[]) { int pid, np, elements_per_process, n_elements_recieved; int partial_sum = 0; //local sums int sum = 0; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &pid); MPI_Comm_size(MPI_COMM_WORLD, &np); // master process if (pid == 0) { int index, i; elements_per_process = n / np; // check if more than 1 processes are run if (np > 1) { // distributes the portion of array for (i = 1; i < np - 1; i++) { index = i * elements_per_process; MPI_Send(&elements_per_process,1, MPI_INT, i, 0,MPI_COMM_WORLD); MPI_Send(&a[index],elements_per_process, MPI_INT, i, 0,MPI_COMM_WORLD); } // last process adds remaining elements index = i * elements_per_process; int elements_left = n - index; MPI_Send(&elements_left,1, MPI_INT,i, 0,MPI_COMM_WORLD); MPI_Send(&a[index], elements_left,MPI_INT, i, 0, MPI_COMM_WORLD); } // 主进程把自己的部分和放到partial_sum里,用于后续归约 for (i = 0; i < elements_per_process; i++) partial_sum += a[i]; } // other processes else { MPI_Recv(&n_elements_recieved, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); // stores the received array segment // in local array a2 MPI_Recv(&a2, n_elements_recieved,MPI_INT, 0, 0,MPI_COMM_WORLD, &status); // calculates its partial sum for (int i = 0; i < n_elements_recieved; i++) partial_sum += a2[i]; } // 修正count参数为1,每个进程归约单个整数 MPI_Reduce(&partial_sum,&sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); // 主进程在归约完成后打印结果 if (pid == 0) { printf("Sum of array is : %d\n", sum); } MPI_Finalize(); return 0; }
关于MPI_Scatter处理可变长度数组的问题
如果想用MPI_Scatter简化代码,当数组长度不能被进程数整除时,用MPI_Scatterv(带v的版本,支持可变长度分发)。步骤如下:
主进程创建两个数组:
sendcounts:每个进程要接收的元素个数,比如n=10,np=3时,sendcounts = [4,3,3]displs:每个进程对应的数组起始位移,比如displs = [0,4,7]
调用MPI_Scatterv分发数组,其他进程接收对应长度的元素。
示例代码片段(主进程部分):
int sendcounts[np]; int displs[np]; int elements_per_process = n / np; int remainder = n % np; for (int i=0; i<np; i++) { sendcounts[i] = elements_per_process; if (i < remainder) { sendcounts[i]++; // 前remainder个进程多拿一个元素 } } displs[0] = 0; for (int i=1; i<np; i++) { displs[i] = displs[i-1] + sendcounts[i-1]; } // 分发数组,主进程用a,其他进程用a2 MPI_Scatterv(a, sendcounts, displs, MPI_INT, a2, sendcounts[pid], MPI_INT, 0, MPI_COMM_WORLD);
这样不管数组长度是奇数还是偶数,都能正确分发到各个进程,之后每个进程计算自己的partial_sum,再用MPI_Reduce汇总即可,比手动Send/Recv简洁很多。
内容的提问来源于stack exchange,提问作者howtofly-lab

