MPI_Scatter用于2维数组和动态分配内存

9
我将尝试使用MPI库编写一个C程序,其中主进程创建一个二维数组并将其行分配给其他进程。该矩阵的维度为p*p,其中p是进程数。
以下是代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>

int **createMatrix (int nrows, int ncols) {
    int **matrix;
    int h, i, j;

    if (( matrix = malloc(nrows*sizeof(int*))) == NULL) {
        printf("Malloc error");
        exit(1);
    }

    for (h=0; h<nrows; h++) {
        if (( matrix[h] = malloc( ncols * sizeof(int))) == NULL) {
            printf("Malloc error 2");
            exit(1);
        }
    }

    for (i=0; i<ncols; i++) {
        for (j=0; j<nrows; j++) {
            matrix[i][j] = ((i*nrows) + j);
        }
    }

    return matrix;
}

void printArray (int *row, int nElements) {
    int i;
    for (i=0; i<nElements; i++) {
        printf("%d ", row[i]);
    }
    printf("\n");
}

void printMatrix (int **matrix, int nrows, int ncols) {
    int i;
    for (i=0; i<nrows; i++) {
        printArray(matrix[i], ncols);
    }
}

int main (int argc, char **argv) {

    if (MPI_Init(&argc, &argv) != MPI_SUCCESS) {
        perror("Error initializing MPI");
        exit(1);
    }

    int p, id;
    MPI_Comm_size(MPI_COMM_WORLD, &p); // Get number of processes
    MPI_Comm_rank(MPI_COMM_WORLD, &id); // Get own ID

    int **matrix;

    if (id == 0) {
        matrix = createMatrix(p, p); // Master process creates matrix
        printf("Initial matrix:\n");
        printMatrix(matrix, p, p);
    }

    int *procRow = malloc(sizeof(int) * p); // received row will contain p integers
    if (procRow == NULL) {
        perror("Error in malloc 3");
        exit(1);
    }

    if (MPI_Scatter(*matrix, p, MPI_INT, // send one row, which contains p integers
                    procRow, p, MPI_INT, // receive one row, which contains p integers
                    0, MPI_COMM_WORLD) != MPI_SUCCESS) {

        perror("Scatter error");
        exit(1);
    }

    printf("Process %d received elements: ", id);
    printArray(procRow, p);

    MPI_Finalize();

    return 0;
}

运行此代码时我收到的输出结果是:
$ mpirun -np 4 test
Initial matrix:
0 1 2 3 
4 5 6 7 
8 9 10 11 
12 13 14 15 
Process 0 received elements: 0 1 2 3 
Process 1 received elements: 1 50 32 97 
Process 2 received elements: -1217693696 1 -1217684120 156314784 
Process 3 received elements: 1 7172196 0 0 

0号进程似乎收到了正确的输入,但其他进程显示的数字我无法理解。此外,请注意,程序的多次运行中,1号和3号进程的数字是一致的,而2号进程的数字在每次运行时都会改变。

我觉得我的内存分配或指针使用有问题,但我对C编程还很陌生。有人能向我解释一下这种输出是如何产生的吗?其次,我也很想知道如何解决我的问题 :) 提前感谢!


请参阅正确分配多维数组 - Lundin
1个回答

19

我认为你基本上误解了scatter操作的作用以及MPI如何期望内存分配和使用。

MPI_Scatter将源数组拆分成多个部分,向MPI通信器的每个成员发送一个唯一的部分。在你的示例中,你需要将矩阵分配为连续的p*p元素的线性存储器,这将发送p个值到每个进程。你的源“矩阵”是一个指针数组。不能保证行在内存中是顺序排列的,MPI_Scatter不知道如何遍历你传递给它的指针数组。因此,该调用只是通过间接引用矩阵指针读取第一行之后的内存,将其作为数据处理。这就是为什么接收数据的进程在第一行之后会收到垃圾值的原因。

所有MPI数据复制例程都期望源和目标内存是“平”的线性数组。多维C数组应该以行主序而不是像你在这里做的那样成为指针数组。为了说明scatter调用的正确工作,可以使用以下简单的hack示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>

int *createMatrix (int nrows, int ncols) {
    int *matrix;
    int h, i, j;

    if (( matrix = malloc(nrows*ncols*sizeof(int))) == NULL) {
        printf("Malloc error");
        exit(1);
    }

    for (h=0; h<nrows*ncols; h++) {
        matrix[h] = h+1;
    }

    return matrix;
}

void printArray (int *row, int nElements) {
    int i;
    for (i=0; i<nElements; i++) {
        printf("%d ", row[i]);
    }
    printf("\n");
}

int main (int argc, char **argv) {

    if (MPI_Init(&argc, &argv) != MPI_SUCCESS) {
        perror("Error initializing MPI");
        exit(1);
    }

    int p, id;
    MPI_Comm_size(MPI_COMM_WORLD, &p); // Get number of processes
    MPI_Comm_rank(MPI_COMM_WORLD, &id); // Get own ID

    int *matrix;

    if (id == 0) {
        matrix = createMatrix(p, p); // Master process creates matrix
        printf("Initial matrix:\n");
        printArray(matrix, p*p);
    }

    int *procRow = malloc(sizeof(int) * p); // received row will contain p integers
    if (procRow == NULL) {
        perror("Error in malloc 3");
        exit(1);
    }

    if (MPI_Scatter(matrix, p, MPI_INT, // send one row, which contains p integers
                procRow, p, MPI_INT, // receive one row, which contains p integers
                0, MPI_COMM_WORLD) != MPI_SUCCESS) {

        perror("Scatter error");
        exit(1);
    }

    printf("Process %d received elements: ", id);
    printArray(procRow, p);

    MPI_Finalize();

    return 0;
}

它会执行以下操作:

$ mpicc -o scatter scatter.c 
$ mpiexec -np 4 scatter
Initial matrix:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
Process 0 received elements: 1 2 3 4 
Process 1 received elements: 5 6 7 8 
Process 2 received elements: 9 10 11 12 
Process 3 received elements: 13 14 15 16 

例如,当您传递存储在线性内存中的数据时,它可以正常工作。相应的行主序数组将像这样静态地分配:

int matrix[4][4] = { {  1,  2,  3,  4 }, 
                     {  5,  6,  7,  8 },
                     {  9, 10, 11, 12 },
                     { 13, 14, 15, 16 } };
请注意静态分配的二维数组和您的代码动态分配的指针数组之间的区别。尽管它们表面上看起来相似,但它们并不是完全相同的东西。

请注意静态分配的二维数组和您的代码动态分配的指针数组之间的区别。尽管它们表面上看起来相似,但它们并不是完全相同的东西。


谢谢你的回复,但是二维矩阵不就是“数组的数组”吗?也就是说,一个3x3矩阵不就是由包含3个元素的三个数组组成的数组吗?按照这种逻辑,我认为MPI_Scatter也应该能够散发我的矩阵中的子数组(即行),但也许这是我的推理错误。另外,你能否解释一下我的程序产生的输出?或者这是错误使用MPI_Scatter的结果吗? - rvw
Scatter期望它散布的数组存储在单个连续的内存分配中。你的“矩阵”实际上是一个指针数组,每个行分配与其他任何行分配没有关系。不能保证它们在内存中相互跟随。你代码中的问题是通过指针间接引用传递了第一行,但是scatter继续读取第一行分配之后的数据,将其视为数据。它对于内存中指针数组的内部布局一无所知,因此它散布到第一行结尾之后的内容只是垃圾。 - talonmies
@rvw:我已经编辑了答案,试图让它更清晰明了。当我最初查看您的代码时,我认为您认为scatter的工作方式更像是send,并且您只想散布第一行,但现在我怀疑这是关于数组以及它们在C中如何存储的误解。希望现在对您来说更有意义了。如果您能点赞/接受,我将不胜感激... - talonmies
非常感谢,你的第二个答案就是我在寻找的答案! - rvw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接