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

MPI矩阵乘法m1函数结果异常:部分数组为空问题求助

MPI矩阵乘法代码中m1数组部分为空的问题排查与解决

问题重现

运行这段基于MPI的矩阵乘法代码时,发现m1的计算结果里,rank 0和最后一个rank之间的部分数组是空值(或垃圾值),多次调试后仍无法解决。

代码问题定位

1. 全局矩阵A、B未在所有进程同步

初始化A、B矩阵时,每个进程只负责初始化自己分配到的行(start_row到end_row),但后续拆分A11、A21等子矩阵时,需要访问整个A、B矩阵的所有行。其他进程没有这些行的数据,导致子矩阵中对应位置是未初始化的垃圾值,最终计算出的m1部分行无效。

2. 子矩阵拆分时的行范围错误

拆分A21、A22时,代码用了A[i+M][j],但i+M对应的行只有当该进程初始化过这些行时才有数据,否则是内存里的随机值。而每个进程只初始化了N/size行,大部分行的数据在当前进程中不存在。

3. multiplym1函数的栈溢出风险

函数里定义了result1[mySize][mySize]和result2[mySize][mySize],mySize是M=500,每个数组占500*500*4=1000000字节(约1MB),两个就是2MB,超出很多系统默认的栈大小,会导致栈溢出,破坏内存数据,进而出现异常结果。

4. MPI_Gatherv的接收缓冲区依赖错误

rank 0用本地的m1作为接收缓冲区,虽然MPI_Gatherv会覆盖对应位置,但前提是其他进程的m1计算正确。但前面的数据同步问题已经导致其他进程的m1数据错误,所以最终合并后的m1中间部分为空/无效。

修复方案

  1. 同步A、B矩阵到所有进程:初始化完成后,用MPI_Allgatherv把每个进程的A、B行数据收集到所有进程,确保每个进程都有完整的A、B矩阵。
  2. 动态分配multiplym1中的临时数组:避免栈溢出,改用malloc分配内存,使用完后释放。
  3. 确保子矩阵拆分时能访问完整数据:所有进程拥有完整A、B矩阵后,拆分子矩阵的逻辑可以正确获取所有行的数据。
  4. 统一分片逻辑:确保每个进程负责的行数计算准确,MPI_Gatherv的counts和displs参数对应正确的偏移量。

完整修复代码

#include <stdlib.h>
#include <time.h>
#include <mpi.h>

#define N 1000
#define M 1000 / 2

int A[N][N], B[N][N], C[N][N];
int m1[M][M], m2[M][M], m3[M][M], m4[M][M], m5[M][M], m6[M][M], m7[M][M];
int A11[M][M], A12[M][M], A21[M][M], A22[M][M], B11[M][M], B12[M][M], B21[M][M], B22[M][M];
int C11[M][M], C12[M][M], C21[M][M], C22[M][M];
int rank, size, start_row, end_row;

void multiplym1(int mySize, int AA[M][M], int BB[M][M], int CC[M][M], int DD[M][M], int resfinal[M][M], int mystart_row, int myend_row)
{
    // 动态分配临时数组,避免栈溢出
    int **result1 = malloc(mySize * sizeof(int*));
    int **result2 = malloc(mySize * sizeof(int*));
    for (int i = 0; i < mySize; i++) {
        result1[i] = malloc(mySize * sizeof(int));
        result2[i] = malloc(mySize * sizeof(int));
    }

    for (int i = mystart_row; i < myend_row; i++)
    {
        for (int j = 0; j < mySize; j++)
        {
            result1[i][j] = AA[i][j] + BB[i][j];
            result2[i][j] = CC[i][j] + DD[i][j];
        }
    }

    for (int i = mystart_row; i < myend_row; i++)
    {
        for (int j = 0; j < mySize; j++)
        {
            resfinal[i][j] = 0;
            for (int k = 0; k < mySize; k++)
            {
                resfinal[i][j] += (result1[i][k] * result2[k][j]);
            }
        }
    }

    // 释放动态分配的内存
    for (int i = 0; i < mySize; i++) {
        free(result1[i]);
        free(result2[i]);
    }
    free(result1);
    free(result2);
}

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

    // 每个进程用不同的随机种子,避免生成相同数据
    srand(time(NULL) + rank);

    // 初始化A、B矩阵的分片行
    int local_N_rows = N / size;
    if (rank == size - 1) {
        local_N_rows += N % size;
    }
    start_row = rank * (N / size);
    end_row = start_row + local_N_rows;

    // 初始化本地负责的行
    for (int i = start_row; i < end_row; i++)
    {
        for (int j = 0; j < N; j++)
        {
            A[i][j] = rand() % 50;
            B[i][j] = rand() % 20;
            C[i][j] = 0;
        }
    }

    // 准备Allgatherv的参数,同步A矩阵到所有进程
    int *counts_A = malloc(size * sizeof(int));
    int *displs_A = malloc(size * sizeof(int));
    int total = 0;
    for (int i = 0; i < size; i++) {
        counts_A[i] = (i == size - 1) ? (N / size + N % size) * N : (N / size) * N;
        displs_A[i] = total;
        total += counts_A[i];
    }
    MPI_Allgatherv(&A[start_row][0], local_N_rows * N, MPI_INT, A, counts_A, displs_A, MPI_INT, MPI_COMM_WORLD);

    // 同步B矩阵到所有进程
    int *counts_B = malloc(size * sizeof(int));
    int *displs_B = malloc(size * sizeof(int));
    total = 0;
    for (int i = 0; i < size; i++) {
        counts_B[i] = (i == size - 1) ? (N / size + N % size) * N : (N / size) * N;
        displs_B[i] = total;
        total += counts_B[i];
    }
    MPI_Allgatherv(&B[start_row][0], local_N_rows * N, MPI_INT, B, counts_B, displs_B, MPI_INT, MPI_COMM_WORLD);

    // 拆分A、B为子矩阵,所有进程都有完整的A、B矩阵,遍历所有行初始化子矩阵
    int local_M_rows = M / size;
    if (rank == size - 1) {
        local_M_rows += M % size;
    }
    start_row = rank * (M / size);
    end_row = start_row + local_M_rows;

    for (int i = 0; i < M; i++)
    {
        for (int j = 0; j < M; j++)
        {
            A11[i][j] = A[i][j];
            A12[i][j] = A[i][j + M];
            A21[i][j] = A[i + M][j];
            A22[i][j] = A[i + M][j + M];

            B11[i][j] = B[i][j];
            B12[i][j] = B[i][j + M];
            B21[i][j] = B[i + M][j];
            B22[i][j] = B[i + M][j + M];
        }
    }

    // 计算m1的本地部分
    multiplym1(M, A11, A22, B11, B22, m1, start_row, end_row);

    // 准备Gatherv的参数,合并m1到rank 0
    int *counts_m1 = malloc(size * sizeof(int));
    int *displs_m1 = malloc(size * sizeof(int));
    total = 0;
    for (int i = 0; i < size; i++) {
        counts_m1[i] = (i == size - 1) ? (M / size + M % size) * M : (M / size) * M;
        displs_m1[i] = total;
        total += counts_m1[i];
    }

    MPI_Gatherv(&m1[start_row][0], local_M_rows * M, MPI_INT, m1, counts_m1, displs_m1, MPI_INT, 0, MPI_COMM_WORLD);

    if (rank == 0)
    {
        printf("合并后的m1部分结果:\n");
        for (int i = 0; i < M; i += 49)
        {
            for (int j = 0; j < M; j += 100)
            {
                printf("m1[%d][%d] = %d\n", i, j, m1[i][j]);
            }
        }
    }

    // 释放内存
    free(counts_A);
    free(displs_A);
    free(counts_B);
    free(displs_B);
    free(counts_m1);
    free(displs_m1);

    MPI_Finalize();
    return 0;
}

修复说明

  • 用MPI_Allgatherv同步A、B矩阵到所有进程,解决子矩阵拆分时的数据缺失问题。
  • 把multiplym1中的临时数组改为动态分配,避免栈溢出导致的内存错误。
  • 给每个进程设置不同的随机种子,避免生成完全相同的矩阵数据。
  • 统一分片计算逻辑,确保MPI_Gatherv的参数准确对应每个进程负责的数据范围。

内容的提问来源于stack exchange,提问作者Mostafa Khastkhodaei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 11:20:28