为包含动态数组的结构创建 MPI 类型

Posted

技术标签:

【中文标题】为包含动态数组的结构创建 MPI 类型【英文标题】:Create MPI type for struct containing dynamic array 【发布时间】:2015-12-31 22:11:15 【问题描述】:

我正在尝试发送一个结构,其中包含一个成员作为动态数组,但该数组似乎没有正确发送。有关如何执行此操作的任何建议?

这就是我所拥有的:

struct bar

    int a;
    int b;
    int* c;
;

void defineMPIType(MPI_Datatype* newType, int cLen, struct bar* msg)

    int blockLengths[3] = 1, 1, cLen;
    MPI_Datatype types[3] = MPI_INT, MPI_INT, MPI_INT;
    MPI_Aint offsets[3];

    MPI_Aint addrB, addrC;
    MPI_Address(&(msg->b), &addrB);
    MPI_Address(msg->c, &addrC);

    offsets[0] = offsetof(struct bar, a);
    offsets[1] = offsetof(struct bar, b);
    offsets[2] = addrC - addrB; 

    MPI_Type_create_struct(3, blockLengths, offsets, types, newType);
    MPI_Type_commit(newType);


void main(int argc, char* argv[])
   
    MPI_Init(&argc, &argv);
    int rank, p;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &p);

    int cLen = argv[0];    
    MPI_Datatype MPI_BAR_TYPE;

    struct bar* msg = malloc(sizeof(*msg)); 
    msg->c =  malloc(sizeof(int) * cLen);
    defineMPIType(&MPI_BAR_TYPE, cLen, msg);

    if (rank == 0)
    
        msg->a = 1;
        msg->b = 2;
        for (int i = 0; i < cLen; ++i)
            msg->c[i] = i;
        MPI_Send(msg, 1, MPI_BAR_TYPE, 1, 111, MPI_COMM_WORLD); 
    
    else
    
        MPI_Status stat;        
        MPI_Recv(msg, 1, MPI_BAR_TYPE, 0, 111, MPI_COMM_WORLD, &stat);      
    

    printf("Rank %d has c = [", rank);
    for (int i = 0; i < cLen; ++i)
        printf("%d, ", msg->c[i]);
    printf("]\n");

    free(msg);
    MPI_Type_free(&MPI_BAR_TYPE);
    MPI_Finalize();

成员 ab 已正确发送,但 c 没有。

【问题讨论】:

一旦知道其大小,您需要使用 MPI 类型连续捕获 c*。 【参考方案1】:

您的代码中存在一些问题,甚至忽略了类型本身的问题:

第一个是您仅在进程#0 上为c 数组分配了内存,然后您(试图)将此数据发送到进程#1。但是进程#1 没有分配任何内存来存储消息。所以即使发送的方式是正确的,代码也会失败。 以MPI_ 开头的名称是为 MPI 库保留的,因此您不能随意使用它们。您必须为您的MPI_BAR_TYPE 找到另一个名称。 这一行让我有些困惑:int cLen = argv[0]; 我想您想从命令行读取要分配的数组的大小,在这种情况下,可能应该读取类似int clen = atoi(argv[1]); 的内容(忘记测试这个的有效性这需要妥善处理...) 您只测试进程是否为 rank #0,这意味着如果由于某种原因您启动了 3 个进程,则 rank #2 进程将永远等待来自 rank #0 进程的消息,该消息永远不会到达. 最后是数组本身:在您的代码中,指针cc 指向的数据之间存在很大的混淆。您的结构嵌入了指针,但没有嵌入指向的内存。所以你不能将相应的数据映射到 MPI 结构中......最明显的原因是从一个调用到下一个(或从一个进程到下一个),不能保证从结构地址的偏移量和c 指向的数据地址将是相同的(实际上,几乎可以保证它会有所不同)。所以你不能可靠地映射它们。

因此,为了解决您的问题,您需要做的是一次性仅传输您的 2 个整数 ab(如果需要,可能创建一个 MPI 结构来传输它们的数组)。然后你将转移c 指向的内存,这是你事先分配好的。

你的代码可以变成例如:

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

struct bar

    int a;
    int b;
    int* c;
;

void defineMPIType( MPI_Datatype* newType ) 
    struct bar tmp[2];
    MPI_Aint extent = &tmp[1] - &tmp[0];

    MPI_Type_create_resized( MPI_2INT, 0, extent, newType );
    MPI_Type_commit( newType );


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

    int cLen = atoi( argv[1] );    
    MPI_Datatype Bar_type;
    defineMPIType( &Bar_type );

    struct bar msg; 
    msg.c = ( int* ) malloc( sizeof( int ) * cLen );
    if ( rank == 0 ) 
        msg.a = 1;
        msg.b = 2;
        for ( int i = 0; i < cLen; ++i ) 
            msg.c[i] = i;
        
        MPI_Send( &msg, 1, Bar_type, 1, 111, MPI_COMM_WORLD );
        MPI_Send( msg.c, cLen, MPI_INT, 1, 222, MPI_COMM_WORLD );
    
    else if ( rank == 1 ) 
        MPI_Recv( &msg, 1, Bar_type, 0, 111, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
        MPI_Recv( msg.c, cLen, MPI_INT, 0, 222, MPI_COMM_WORLD, MPI_STATUS_IGNORE );
    

    printf("Rank %d has a = %d, b = %d, c = [", rank, msg.a, msg.b );
    for ( int i = 0; i < cLen - 1; ++i ) 
         printf( "%d, ", msg.c[i] );
    
    printf( "%d]\n", msg.c[cLen - 1] );

    free( msg.c );
    MPI_Type_free( &Bar_type );
    MPI_Finalize();

    return 0;

这给出了:

$ mpirun -n 2 ./a.out 3
Rank 0 has a = 1, b = 2, c = [0, 1, 2]
Rank 1 has a = 1, b = 2, c = [0, 1, 2]

快乐的 MPI 编码。

【讨论】:

感谢您的回答!是的,我的示例有些混乱,因为我试图从真实代码中省略一些不相关的内容(例如,cLen 是从其他地方传入的,而不是命令行)。无论如何,关于你的方法,是的,我以前做过两次 MPI_Sends,但我尽量不这样做。我正在做一个我们不能假设消息传递顺序的项目。另外,如果我发送多条消息,我不希望将 a 和 b 与正确的 c 匹配。 我已经用一种发送一条消息的方式更新了代码,但似乎c 数组的最后一个元素是唯一没有正确发送的元素。跨度> 这行不通,因为正如我告诉你的那样,你不能假设 c 指向的内存相对于指针 c 本身的特定内存位置......所以它不会工作!如果您不能在结构中分配静态内存,则必须在两个单独的消息中传输结构和数据(除非您手动将数据打包到缓冲区中,可能使用MPI_Pack,但我不会推荐)。

以上是关于为包含动态数组的结构创建 MPI 类型的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 MPI 传输带有动态数组的自定义结构?

使用 malloc 的 MPI 动态数组

使用 MPI 派生数据类型创建和通信“结构数组”

MPI_Bcast 动态二维数组

如何为 c++ 的不同变量类型的结构元素创建动态数组?

具有动态分配成员的动态分配结构的 MPI 派生数据类型