MPI主进程向从进程传递子矩阵的实现咨询
解决MPI子矩阵最大和的任务分配与数据传递问题
你好!看你正在做MPI环境下的子矩阵最大和问题,我来帮你理清任务分配和数据传递的核心思路,再结合你的代码框架给出修改后的实现示例。
一、子矩阵的任务分配策略
首先得明确总共有多少个待计算的目标子矩阵:
假设目标子矩阵的高度是 h = i2 - i1 + 1,宽度是 w = j2 - j1 + 1,那么n×n的原矩阵中,合法的子矩阵总数为:
s = (n - h + 1) * (n - w + 1)
关于任务分配,不建议直接按N/s硬拆分(除非s刚好能被进程数整除),更合理的是采用**「分块+补余」**的均衡分配方式:
- 每个进程的基础任务数:
base = s / numprocs - 剩余未分配的任务数:
remainder = s % numprocs - 前
remainder个从进程(rank 1到rank remainder)多承担1个任务,即负责base + 1个子矩阵 - 剩下的进程负责
base个子矩阵
这种方式能避免负载不均的问题,比如s=100、numprocs=3时,前2个进程各做34个,最后1个做32个,比硬拆33+33+34更均衡。
另外,不需要把每个子矩阵单独传给进程——直接传递整个原矩阵给所有进程,再告诉每个进程它需要处理哪些子矩阵的起始坐标即可。这样能大幅减少通信量,因为每个子矩阵都是原矩阵的切片,重复传递子矩阵会造成大量冗余数据。
二、数据传递的核心步骤
- 主进程广播全局数据:用
MPI_Bcast广播n、h、w这些公共参数,再用MPI_Send把整个原矩阵发送给所有从进程 - 主进程分配任务范围:给每个从进程发送它需要处理的子矩阵的起始/结束索引(线性索引,后续转换为坐标)
- 从进程计算局部最大值:根据收到的任务范围,遍历每个子矩阵的左上角坐标,计算元素和并记录局部最大的结果
- 从进程返回结果:把局部最大值和对应子矩阵的坐标发送回主进程
- 主进程汇总全局最大值:收集所有从进程的结果,比较得到全局最优解
三、修改后的完整代码示例
下面是基于你的框架修改后的代码,关键逻辑都加了注释:
#include<mpi.h> #include<stdio.h> #include<math.h> #include<assert.h> #include<iostream> #include<cstdlib> #include<ctime> using namespace std; #pragma comment (lib, "msmpi.lib") enum CommunicationTag { COMM_TAG_MASTER_SEND_GLOBAL_DATA, COMM_TAG_MASTER_SEND_TASK_RANGE, COMM_TAG_SLAVE_SEND_RESULT, }; // 打印矩阵工具函数 void print_matrix(int mat[10][10], int n) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { printf("%d ", mat[i][j]); } printf("\n"); } } // 计算单个子矩阵的元素和 int calculate_submatrix_sum(int mat[10][10], int start_i, int start_j, int h, int w) { int sum = 0; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { sum += mat[start_i + i][start_j + j]; } } return sum; } int main(int argc, char *argv[]) { int numprocs, rank, rc; rc = MPI_Init(&argc, &argv); if (rc != MPI_SUCCESS) { printf("Error starting MPI program. Terminating \n"); MPI_Abort(MPI_COMM_WORLD, rc); } MPI_Comm_size(MPI_COMM_WORLD, &numprocs); MPI_Comm_rank(MPI_COMM_WORLD, &rank); srand(time(0) + rank); // 每个进程用不同随机种子,避免重复随机数 if (rank == 0) { int n; scanf("%d", &n); int i1, i2, j1, j2; scanf("%d%d%d%d", &i1, &i2, &j1, &j2); int h = i2 - i1 + 1; int w = j2 - j1 + 1; int mat[10][10]; // 初始化原矩阵(-50到49的随机整数) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { mat[i][j] = (rand() % 100) - 50; } printf("Original Matrix:\n"); print_matrix(mat, n); // 1. 广播全局参数给所有进程 MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&h, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&w, 1, MPI_INT, 0, MPI_COMM_WORLD); // 发送原矩阵给每个从进程 for (int i = 1; i < numprocs; i++) { MPI_Send(mat, n*n, MPI_INT, i, COMM_TAG_MASTER_SEND_GLOBAL_DATA, MPI_COMM_WORLD); } // 2. 计算总任务数并分配任务范围 int total_tasks = (n - h + 1) * (n - w + 1); int base = total_tasks / numprocs; int remainder = total_tasks % numprocs; // 主进程自身也参与计算,充分利用资源 int local_max_sum = -1e9; int local_best_i = 0, local_best_j = 0; int start_idx = 0; int end_idx = (rank < remainder) ? base : base - 1; int row_count = n - h + 1; for (int idx = start_idx; idx <= end_idx; idx++) { // 把线性索引转换为子矩阵左上角坐标(i,j) int i = idx / row_count; int j = idx % row_count; int sum = calculate_submatrix_sum(mat, i, j, h, w); if (sum > local_max_sum) { local_max_sum = sum; local_best_i = i; local_best_j = j; } } // 给从进程分配任务范围 for (int proc = 1; proc < numprocs; proc++) { int proc_start, proc_end; if (proc <= remainder) { proc_start = (proc-1)*(base+1) + (base+1); proc_end = proc_start + base; } else { proc_start = remainder*(base+1) + (proc - remainder -1)*base + (base+1); proc_end = proc_start + base -1; } proc_end = min(proc_end, total_tasks -1); // 防止超出总任务数 MPI_Send(&proc_start, 1, MPI_INT, proc, COMM_TAG_MASTER_SEND_TASK_RANGE, MPI_COMM_WORLD); MPI_Send(&proc_end, 1, MPI_INT, proc, COMM_TAG_MASTER_SEND_TASK_RANGE, MPI_COMM_WORLD); } // 3. 收集所有从进程的结果,汇总全局最大值 int global_max_sum = local_max_sum; int global_best_i = local_best_i; int global_best_j = local_best_j; for (int proc = 1; proc < numprocs; proc++) { int slave_sum, slave_i, slave_j; MPI_Recv(&slave_sum, 1, MPI_INT, proc, COMM_TAG_SLAVE_SEND_RESULT, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(&slave_i, 1, MPI_INT, proc, COMM_TAG_SLAVE_SEND_RESULT, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(&slave_j, 1, MPI_INT, proc, COMM_TAG_SLAVE_SEND_RESULT, MPI_COMM_WORLD, MPI_STATUS_IGNORE); if (slave_sum > global_max_sum) { global_max_sum = slave_sum; global_best_i = slave_i; global_best_j = slave_j; } } // 输出最终结果 printf("\nGlobal Maximum Submatrix Sum: %d\n", global_max_sum); printf("Top-left coordinate of best submatrix: (%d, %d)\n", global_best_i, global_best_j); printf("Best Submatrix:\n"); for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { printf("%d ", mat[global_best_i + i][global_best_j + j]); } printf("\n"); } } else { // 从进程逻辑 int n, h, w; // 接收全局参数 MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&h, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&w, 1, MPI_INT, 0, MPI_COMM_WORLD); // 接收原矩阵 int mat[10][10]; MPI_Recv(mat, n*n, MPI_INT, 0, COMM_TAG_MASTER_SEND_GLOBAL_DATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE); // 接收任务范围 int start_idx, end_idx; MPI_Recv(&start_idx, 1, MPI_INT, 0, COMM_TAG_MASTER_SEND_TASK_RANGE, MPI_COMM_WORLD, MPI_STATUS_IGNORE); MPI_Recv(&end_idx, 1, MPI_INT, 0, COMM_TAG_MASTER_SEND_TASK_RANGE, MPI_COMM_WORLD, MPI_STATUS_IGNORE); // 计算局部最大值 int local_max_sum = -1e9; int local_best_i = 0, local_best_j = 0; int row_count = n - h + 1; for (int idx = start_idx; idx <= end_idx; idx++) { int i = idx / row_count; int j = idx % row_count; int sum = calculate_submatrix_sum(mat, i, j, h, w); if (sum > local_max_sum) { local_max_sum = sum; local_best_i = i; local_best_j = j; } } // 返回局部结果给主进程 MPI_Send(&local_max_sum, 1, MPI_INT, 0, COMM_TAG_SLAVE_SEND_RESULT, MPI_COMM_WORLD); MPI_Send(&local_best_i, 1, MPI_INT, 0, COMM_TAG_SLAVE_SEND_RESULT, MPI_COMM_WORLD); MPI_Send(&local_best_j, 1, MPI_INT, 0, COMM_TAG_SLAVE_SEND_RESULT, MPI_COMM_WORLD); } MPI_Finalize(); return 0; }
关键细节说明
- 通信优化:用
MPI_Bcast广播公共参数比单独发送更高效;原矩阵只传递一次,避免了子矩阵的冗余传输。 - 负载均衡:通过补余分配策略,让每个进程的任务量尽可能均衡,避免部分进程闲置。
- 索引转换:将线性任务索引转换为子矩阵坐标,简化了遍历逻辑。
- 主进程参与计算:让主进程也承担部分任务,充分利用所有计算资源(如果只需要主进程做调度,可删除这部分逻辑)。
内容的提问来源于stack exchange,提问作者N. Nae
相关产品推荐
相关产品推荐

