使用 MPI 数据类型无法接收数组的子集

Trouble receiving a subset of an array using MPI Datatypes

我在发送和接收二维数组的列时遇到问题。

我有 2 个进程。第一个进程有一个二维数组,我想将它的一部分发送到第二个进程。所以说每个等级都有一个 9x9 数组,我想将等级 0 发送到等级 1 只是某些列:

示例:

-1--2--3-
-2--3--4-
-5--6--7- ...

我要发送“1,2,5,...”和“3,4,7,...”。

我编写了仅发送第一列的代码,并且通读了 this answer,我相信我已经为该列正确定义了 MPI_Type_vector:

MPI_Type_vector(dime,1,dime-1,MPI_INT,&LEFT_SIDE);

其中dime这里,9,是数组的大小;我发送 9 个块,每块 1 MPI_INT,每个块以 8 的步幅分隔 - 但即使只发送这一列也会给我无效结果。

我的代码如下:

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

#define dime 9

int main (int argc, char *argv[])
{
    int size,rank;
    const int ltag=2;

    MPI_Init(&argc,&argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);       // Get the number of processes
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);       // Get the rank of the process

    int table[dime][dime];
    for (int i=0; i<dime; i++)
        for (int j=0; j<dime; j++)
            table[i][j] = rank;

    int message[dime];

    MPI_Datatype LEFT_SIDE;
    MPI_Type_vector(dime,1,dime-1,MPI_INT,&LEFT_SIDE);
    MPI_Type_commit(&LEFT_SIDE);

    if(rank==0) {
        MPI_Send(table, 1, LEFT_SIDE, 1, ltag, MPI_COMM_WORLD);
    } else if(rank==1){
        MPI_Status status;
        MPI_Recv(message, 1, LEFT_SIDE, 0, ltag, MPI_COMM_WORLD, &status);
    }

    if(rank == 1 ){
        printf("Rank 1's received data: ");

        for(int i=0;i<dime;i++)
            printf("%6d ",*(message+i));

        printf("\n");
    }

    MPI_Finalize();
    return 0;

}

但是当我 运行 它并查看我收到的数据时,我得到的不是全零就是乱码:

$ mpicc -o datatype datatype.c -Wall -g -O3 -std=c99 
$ mpirun -np 2 datatype
Rank 1's received data:      0  32710 64550200      0 1828366128  32765 11780096      0      0 

数字每次都在变化。我做错了什么?

我不太确定你的问题到底是什么(请在你的问题中明确说明,你会得到更好的答案!另请参阅 How do I ask good questions),但你的代码有几个问题。

  • 您需要使用MPI_Type_vector(dime,1,dime,MPI_INT,&LEFT_SIDE);,因为您要发送矩阵的每个 dime-th 元素。在 C 中,二维数组只是作为标准数组存储,元素 [i][j] 存储在索引 [i*dime+j] 处。您想要发送索引为 0、dime、2*dime、3*dime、...

  • 的元素
  • 如果您使用 LEFT_SIDE 数据类型来接收数据,MPI 将以 dime 元素的间隙存储您的数据项 - 类似于发送方。但是,您的接收缓冲区 message 是一个简单的数组。您需要接收这样的数据:MPI_Recv(message, dime, MPI_INT, 0, LTAG, newcomm,&status);。此操作将接收一角整数并将它们放入您的 message 数组中。

编辑: 我更新了我的答案以匹配显着变化的问题。

@Mort 的回答是正确的,是第一个;我只是想用一些 ASCII 艺术图对其进行扩展,以尝试传达他的信息。

MPI 数据类型描述了数据在内存中的布局方式。让我们看一下您的二维数组以获得较小的 dime (比如 4)和相应的 MPI_Type_vector:

 MPI_Type_vector(count=dime, blocksize=1, stride=dime-1, type=MPI_INT ...
                      = 4             =1        = 3

 data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 };
 Vector:  X  -  -  X  -  -  X  -  -  X -  -

请注意,MPI 类型中的步幅是类型 starts 之间的距离,而不是它们之间的间隙大小;所以你实际上想要 stride=dime 在这里,而不是 dime-1。这很容易解决,但这不是真正的问题:

 MPI_Type_vector(count=dime, blocksize=1, stride=dime, type=MPI_INT ...
                      = 4             =1        = 4

 data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 };
 Vector:  X  -  -  -  X  -  -  -  X  -  -  -  X -  -  - 

好的,到目前为止一切顺利,我们正在选择正确的元素。但是我们没有正确地接收它们;代码试图将数据接收到一角硬币大小的数组中,使用相同的布局:

int message[dime];
MPI_Recv(message, 1, LEFT_SIDE, 0, ...

message = { 0, 1, 2, 3 };
Vector:     X  -  -  -  X  -  -  -  X  -  -  -  X -  -  - 

向量超出了消息的范围,(a) 在消息中留下未初始化的数据,这是乱码的来源,(b) 可能导致超出数组边界的分段错误。

至关重要的是,其中一个 MPI_Type_vector 描述了二维矩阵中所需数据的布局,但 描述了与它相同的数据的布局接收到一个紧凑的一维数组。

这里有两个选择。将数据简单地接收到 message 数组中 dime x MPI_INT:

// ....
} else if(rank==1){
    MPI_Status status;
    MPI_Recv(message, dime, MPI_INT, 0, ltag, MPI_COMM_WORLD, &status);
}

//...

$ mpirun -np 2 datatype
Rank 1's received data:      0      0      0      0      0      0      0      0      0 

或者直接将数据接收到 Rank 1 的 2d 矩阵中,覆盖相应的列:

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

#define dime 9

int main (int argc, char *argv[])
{
    int size,rank;
    const int ltag=2;

    MPI_Init(&argc,&argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);       // Get the number of processes
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);       // Get the rank of the process

    int table[dime][dime];
    for (int i=0; i<dime; i++)
        for (int j=0; j<dime; j++)
            table[i][j] = rank;

    MPI_Datatype LEFT_SIDE;
    MPI_Type_vector(dime,1,dime,MPI_INT,&LEFT_SIDE);
    MPI_Type_commit(&LEFT_SIDE);

    if(rank==0) {
        MPI_Send(table, 1, LEFT_SIDE, 1, ltag, MPI_COMM_WORLD);
    } else if(rank==1){
        MPI_Status status;
        MPI_Recv(table, 1, LEFT_SIDE, 0, ltag, MPI_COMM_WORLD, &status);
    }

    if(rank == 1 ){
        printf("Rank 1's new array:\n");

        for(int i=0;i<dime;i++) {
            for(int j=0;j<dime;j++) 
                printf("%6d ",table[i][j]);
            printf("\n");
        }

        printf("\n");
    }

    MPI_Type_free(&LEFT_SIDE);
    MPI_Finalize();
    return 0;

}

运行 给出

$ mpicc -o datatype datatype.c -Wall -g -O3 -std=c99 
$ mpirun -np 2 datatype
Rank 1's new array:
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 
     0      1      1      1      1      1      1      1      1 

(更正 MPI_Type_vector 后)

关于如何将其扩展到多列的其余部分可能最好留给另一个问题。