MPI矩阵乘法:进程数小于矩阵行数时的任务分配咨询
基于MPI的矩阵乘法:进程数小于矩阵行数的处理方案
针对你提到的4进程处理8×8矩阵的场景,核心思路是让每个进程处理多行任务,而非仅分配一行,这样可以避免多次分配带来的同步问题。以下是两种可行的实现方式,均仅使用MPI_Send和MPI_Recv接口:
一、静态分片分配(推荐,实现简单)
直接按进程数平均划分矩阵A的行,每个进程一次性拿到所有需要处理的行块,独立计算后返回结果,全程无需中途同步。
具体步骤
计算分片规则:
- 设矩阵A的行数为
N,进程数为P - 基础行数:
base = N / P - 剩余行数:
remainder = N % P - 前
remainder个进程多处理1行,即这些进程的任务行数为base + 1,其余进程为base - 以8×8矩阵、4进程为例:
base=2,remainder=0,每个进程处理2行
- 设矩阵A的行数为
主进程(rank 0)逻辑:
- 给每个子进程发送:要处理的起始行号、任务行数、完整的矩阵B(每个进程计算都需要整个B)、对应的A矩阵行块
- 主进程自行分配并处理自己的行块
- 逐个接收子进程返回的结果行块,拼接成最终结果矩阵
子进程逻辑:
- 接收主进程发来的任务参数和数据
- 计算分配到的行的结果
- 将结果行块发回主进程
代码片段示例
#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; }
二、动态任务分配(适合负载不均场景)
如果需要更灵活的任务分配(比如矩阵行数不确定),可以让主进程逐个发放任务,子进程完成当前任务后主动请求新任务,直到所有行处理完毕。
核心逻辑
- 主进程:
- 先给每个子进程分配初始行(比如前4行)
- 循环接收任意子进程的结果,保存后立即分配新的未处理行
- 所有行分配完成后,给子进程发送结束信号(比如行号设为-1)
- 子进程:
- 循环接收任务行号,若为结束信号则退出
- 计算该行的结果,发回主进程后继续请求新任务
代码片段示例
#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
相关产品推荐
相关产品推荐

