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

MPI矩阵乘法:进程数小于矩阵行数时的任务分配咨询

基于MPI的矩阵乘法:进程数小于矩阵行数的处理方案

针对你提到的4进程处理8×8矩阵的场景,核心思路是让每个进程处理多行任务,而非仅分配一行,这样可以避免多次分配带来的同步问题。以下是两种可行的实现方式,均仅使用MPI_Send和MPI_Recv接口:

一、静态分片分配(推荐,实现简单)

直接按进程数平均划分矩阵A的行,每个进程一次性拿到所有需要处理的行块,独立计算后返回结果,全程无需中途同步。

具体步骤

  1. 计算分片规则:

    • 设矩阵A的行数为N,进程数为P
    • 基础行数:base = N / P
    • 剩余行数:remainder = N % P
    • 前remainder个进程多处理1行,即这些进程的任务行数为base + 1,其余进程为base
    • 以8×8矩阵、4进程为例:base=2,remainder=0,每个进程处理2行
  2. 主进程(rank 0)逻辑:

    • 给每个子进程发送:要处理的起始行号、任务行数、完整的矩阵B(每个进程计算都需要整个B)、对应的A矩阵行块
    • 主进程自行分配并处理自己的行块
    • 逐个接收子进程返回的结果行块,拼接成最终结果矩阵
  3. 子进程逻辑:

    • 接收主进程发来的任务参数和数据
    • 计算分配到的行的结果
    • 将结果行块发回主进程

代码片段示例

#include <mpi.h>
#include <cstring>

const int N = 8; // 矩阵维度

int main(int argc, char** argv) {
    MPI_Init(&argc, &argv);
    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    if (rank == 0) {
        int A[N][N], B[N][N], C[N][N];
        // 初始化矩阵A、B(示例省略填充逻辑)

        int base = N / size;
        int remainder = N % size;

        // 给子进程分配任务
        for (int i = 1; i < size; ++i) {
            int start_row = i * base + (i < remainder ? i : remainder);
            int num_rows = base + (i < remainder ? 1 : 0);
            
            MPI_Send(&start_row, 1, MPI_INT, i, 0, MPI_COMM_WORLD);
            MPI_Send(&num_rows, 1, MPI_INT, i, 0, MPI_COMM_WORLD);
            MPI_Send(B, N*N, MPI_INT, i, 0, MPI_COMM_WORLD);
            MPI_Send(&A[start_row][0], num_rows*N, MPI_INT, i, 0, MPI_COMM_WORLD);
        }

        // 主进程处理自己的任务
        int my_start = 0;
        int my_num = base + (0 < remainder ? 1 : 0);
        for (int i = my_start; i < my_start + my_num; ++i) {
            memset(C[i], 0, sizeof(int)*N);
            for (int j = 0; j < N; ++j) {
                for (int k = 0; k < N; ++k) {
                    C[i][j] += A[i][k] * B[k][j];
                }
            }
        }

        // 接收子进程结果
        for (int i = 1; i < size; ++i) {
            int start_row, num_rows;
            MPI_Recv(&start_row, 1, MPI_INT, i, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            MPI_Recv(&C[start_row][0], num_rows*N, MPI_INT, i, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        }

        // 输出或保存结果(示例省略)
    } else {
        int start_row, num_rows;
        MPI_Recv(&start_row, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        MPI_Recv(&num_rows, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

        int B[N][N];
        MPI_Recv(B, N*N, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

        int* A_block = new int[num_rows * N];
        MPI_Recv(A_block, num_rows*N, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

        int* C_block = new int[num_rows * N];
        memset(C_block, 0, sizeof(int)*num_rows*N);
        for (int i = 0; i < num_rows; ++i) {
            for (int j = 0; j < N; ++j) {
                for (int k = 0; k < N; ++k) {
                    C_block[i*N + j] += A_block[i*N + k] * B[k][j];
                }
            }
        }

        // 返回结果给主进程
        MPI_Send(&start_row, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
        MPI_Send(C_block, num_rows*N, MPI_INT, 0, 1, MPI_COMM_WORLD);

        delete[] A_block;
        delete[] C_block;
    }

    MPI_Finalize();
    return 0;
}

二、动态任务分配(适合负载不均场景)

如果需要更灵活的任务分配(比如矩阵行数不确定),可以让主进程逐个发放任务,子进程完成当前任务后主动请求新任务,直到所有行处理完毕。

核心逻辑

  1. 主进程:
    • 先给每个子进程分配初始行(比如前4行)
    • 循环接收任意子进程的结果,保存后立即分配新的未处理行
    • 所有行分配完成后,给子进程发送结束信号(比如行号设为-1)
  2. 子进程:
    • 循环接收任务行号,若为结束信号则退出
    • 计算该行的结果,发回主进程后继续请求新任务

代码片段示例

#include <mpi.h>
#include <cstring>

const int N = 8;

int main(int argc, char** argv) {
    MPI_Init(&argc, &argv);
    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    if (rank == 0) {
        int A[N][N], B[N][N], C[N][N];
        // 初始化矩阵A、B

        // 发放初始任务
        for (int i = 1; i < size; ++i) {
            int row = i - 1;
            MPI_Send(&row, 1, MPI_INT, i, 0, MPI_COMM_WORLD);
            MPI_Send(A[row], N, MPI_INT, i, 0, MPI_COMM_WORLD);
            MPI_Send(B, N*N, MPI_INT, i, 0, MPI_COMM_WORLD);
        }

        int remaining_row = size;
        // 处理结果并分配剩余任务
        while (remaining_row < N) {
            MPI_Status status;
            int done_row;
            int result[N];
            MPI_Recv(&done_row, 1, MPI_INT, MPI_ANY_SOURCE, 1, MPI_COMM_WORLD, &status);
            MPI_Recv(result, N, MPI_INT, status.MPI_SOURCE, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            memcpy(C[done_row], result, sizeof(int)*N);

            // 发放新任务
            int new_row = remaining_row++;
            MPI_Send(&new_row, 1, MPI_INT, status.MPI_SOURCE, 0, MPI_COMM_WORLD);
            MPI_Send(A[new_row], N, MPI_INT, status.MPI_SOURCE, 0, MPI_COMM_WORLD);
        }

        // 接收最后一批结果并发送结束信号
        for (int i = 1; i < size; ++i) {
            MPI_Status status;
            int done_row;
            int result[N];
            MPI_Recv(&done_row, 1, MPI_INT, i, 1, MPI_COMM_WORLD, &status);
            MPI_Recv(result, N, MPI_INT, i, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            memcpy(C[done_row], result, sizeof(int)*N);

            int end_signal = -1;
            MPI_Send(&end_signal, 1, MPI_INT, i, 0, MPI_COMM_WORLD);
        }
    } else {
        while (true) {
            MPI_Status status;
            int row;
            MPI_Recv(&row, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
            if (row == -1) break;

            int A_row[N], B[N][N];
            MPI_Recv(A_row, N, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            MPI_Recv(B, N*N, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

            int result[N] = {0};
            for (int j = 0; j < N; ++j) {
                for (int k = 0; k < N; ++k) {
                    result[j] += A_row[k] * B[k][j];
                }
            }

            MPI_Send(&row, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
            MPI_Send(result, N, MPI_INT, 0, 1, MPI_COMM_WORLD);
        }
    }

    MPI_Finalize();
    return 0;
}

关键注意事项

  • 静态分片无需中途同步,所有进程一次性完成任务,是最稳定的方案
  • 动态分配需使用MPI_ANY_SOURCE接收任意进程的结果,避免主进程阻塞等待特定进程
  • 两种方案均需确保MPI_Send和MPI_Recv的标签、数据类型匹配,防止死锁或数据错误

内容的提问来源于stack exchange,提问作者Muhammad Waseem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 04:45:30