You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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更均衡。

另外,不需要把每个子矩阵单独传给进程——直接传递整个原矩阵给所有进程,再告诉每个进程它需要处理哪些子矩阵的起始坐标即可。这样能大幅减少通信量,因为每个子矩阵都是原矩阵的切片,重复传递子矩阵会造成大量冗余数据。

二、数据传递的核心步骤

  1. 主进程广播全局数据:用MPI_Bcast广播n、h、w这些公共参数,再用MPI_Send把整个原矩阵发送给所有从进程
  2. 主进程分配任务范围:给每个从进程发送它需要处理的子矩阵的起始/结束索引(线性索引,后续转换为坐标)
  3. 从进程计算局部最大值:根据收到的任务范围,遍历每个子矩阵的左上角坐标,计算元素和并记录局部最大的结果
  4. 从进程返回结果:把局部最大值和对应子矩阵的坐标发送回主进程
  5. 主进程汇总全局最大值:收集所有从进程的结果,比较得到全局最优解

三、修改后的完整代码示例

下面是基于你的框架修改后的代码,关键逻辑都加了注释:

#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:48:33