C中的结构序列化并通过MPI传输

Posted

技术标签:

【中文标题】C中的结构序列化并通过MPI传输【英文标题】:struct serialization in C and transfer over MPI 【发布时间】:2012-04-09 12:10:09 【问题描述】:

我已经定义了一个自定义struct,我需要将它发送给另一个 MPI 进程使用MPI_Bsend(或MPI_Send)。

这是结构:

struct car
  int shifts;
  int topSpeed;
myCar;

问题在于,除了原始类型之外,MPI 似乎不支持像上面显示的结构那样直接“传输”复杂数据类型。我听说我可能不得不使用“序列化”。

我应该如何处理这个问题并成功地将myCar 发送到进程 5?

【问题讨论】:

【参考方案1】:

Jeremiah 是对的 - MPI_Type_create_struct 是通往这里的路。

重要的是要记住 MPI 是一个库,而不是内置于语言中;所以它不能“看到”一个结构本身序列化它的样子。因此,要发送复杂的数据类型,您必须明确定义其布局。在一种确实支持序列化的语言中,一组 MPI 包装器可以想象地利用它;例如mpi4py利用python的pickle透明发送复杂数据类型;但在 C 中,你必须卷起袖子自己动手。

对于您的结构,它看起来像这样:

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

typedef struct car_s 
        int shifts;
        int topSpeed;
 car;

int main(int argc, char **argv) 

    const int tag = 13;
    int size, rank;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    if (size < 2) 
        fprintf(stderr,"Requires at least two processes.\n");
        exit(-1);
    

    /* create a type for struct car */
    const int nitems=2;
    int          blocklengths[2] = 1,1;
    MPI_Datatype types[2] = MPI_INT, MPI_INT;
    MPI_Datatype mpi_car_type;
    MPI_Aint     offsets[2];

    offsets[0] = offsetof(car, shifts);
    offsets[1] = offsetof(car, topSpeed);

    MPI_Type_create_struct(nitems, blocklengths, offsets, types, &mpi_car_type);
    MPI_Type_commit(&mpi_car_type);

    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    if (rank == 0) 
        car send;
        send.shifts = 4;
        send.topSpeed = 100;

        const int dest = 1;
        MPI_Send(&send,   1, mpi_car_type, dest, tag, MPI_COMM_WORLD);

        printf("Rank %d: sent structure car\n", rank);
    
    if (rank == 1) 
        MPI_Status status;
        const int src=0;

        car recv;

        MPI_Recv(&recv,   1, mpi_car_type, src, tag, MPI_COMM_WORLD, &status);
        printf("Rank %d: Received: shifts = %d topSpeed = %d\n", rank,
                 recv.shifts, recv.topSpeed);
    

    MPI_Type_free(&mpi_car_type);
    MPI_Finalize();

    return 0;

【讨论】:

感谢您非常全面和快速的回​​复。对此,我真的非常感激。你已经完全覆盖了我。 (但是我认为您忘记将 放在首位,否则编译器会出错..) 你是对的 - offsetof() 需要。我已经适当地更新了代码。 感谢这个有用的答案。我有一个问题:如果结构的成员之一,比如shifts,是一个数组,应该如何更改你的示例? (int shifts[2];)【参考方案2】:

虽然 Jonathan Dursi 的答案是正确的,但它过于复杂。 MPI 提供了更简单、更通用的类型构造函数,更适合您的问题。 MPI_Type_create_struct 仅在您具有不同的基本类型(例如,int 和 float)时才需要。

对于您的示例,存在几个更好的解决方案:

假设这两个整数在一个连续的内存区域中对齐(即,就像一个整数数组),您根本不需要派生数据类型。只需使用car 类型变量的地址发送/接收两个MPI_INT 类型的元素,用作发送/接收缓冲区:

MPI_Send(&send, 2, MPI_INT, dest, tag, MPI_COMM_WORLD);
MPI_Recv(&recv, 2, MPI_INT, src, tag, MPI_COMM_WORLD, &status);

如果你想使用派生数据类型(例如,为了可读性或它的乐趣),你可以使用对应于数组的MPI_Type_contiguous

MPI_Type_contiguous(2, MPI_INT, &mpi_car_type);

如果两个整数的对齐方式不同(很可能不是这种情况,但它依赖于机器并且存在许多不同平台的 MPI 实现),您可以使用 MPI_Type_indexed_block:它需要一个位移数组(例如MPI_Type_create_struct),但只有一个 oldtype 参数,并且每个块的块长度定义为 1:

MPI_Aint offsets[2];
offsets[0] = offsetof(car, shifts) ; //most likely going to be 0 
offsets[1] = offsetof(car, topSpeed);
MPI_Type_indexed_block(2, offsets, MPI_INT);

虽然另一种解决方案在语义上是正确的,但它更难阅读,并且可能会导致很大的性能损失。

【讨论】:

【参考方案3】:

查看MPI_Type_create_struct 为您的对象构建自定义 MPI 数据类型。使用它的一个例子是http://beige.ucs.indiana.edu/I590/node100.html

【讨论】:

我还是有点迷茫..!假设我定义了 MPI 结构,现在想使用它。您提供的链接状态: MPI_Type_create_struct(5, array_of_block_lengths, array_of_displacements, array_of_types, &new_type);我现在应该做类似 myCar=&new_type 的事情吗? 还有更重要的...请给我一个创建和传输特定结构的简单但具体的例子吗? 问题已解决。您提供的链接提供了所有“理论”,但由于位移和低级细节可能很容易使业余程序员感到困惑。然而,它似乎准确地描述了其背后的机制。 链接已损坏。你能在你的回答中举个例子吗?【参考方案4】:
int MPI_Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)

OpenMPI 将发送从 buf 开始的连续字节 count * sizeof(datatype) 以允许发送诸如 int 数组之类的内容。例如,如果你声明一个 10 int 数组int arr[10],你可以用

MPI_Send(arr, 10, MPI_INT, 1, 0, MPI_COMM_WORLD);

并以类似方式接收。由于buf 是一个空指针,我们可以通过发送sizeof(my_struct) 字节并在接收端作为结构回退来滥用它来发送结构。这是一个例子:

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

typedef struct 

    char a;
    int b;
    short c;
 my_struct;


int main (int argc, char *argv[])

    int  numtasks, taskid;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &taskid);
    MPI_Comm_size(MPI_COMM_WORLD, &numtasks);


    if (taskid == 0) 
    
        my_struct m;
        m.a = '!';
        m.b = 1234;
        m.c = 5678;

        MPI_Send(&m, sizeof(my_struct), MPI_CHAR, 1, 0, MPI_COMM_WORLD);
    
    else 
    
        my_struct m;
        MPI_Recv(&m, sizeof(my_struct), MPI_CHAR, 0, 0, MPI_COMM_WORLD, 
                 MPI_STATUS_IGNORE);
        printf("%c %d %d\n", m.a, m.b, m.c); 
    

    MPI_Finalize();

由于 C 数组连续存储数据,我们甚至可以像 malloc an array of structs 那样发送结构数组。因此,如果您有 my_struct m_array[10],您将发送(并以类似方式接收)

MPI_Send(m_array, sizeof(my_struct) * 10, MPI_CHAR, 1, 0, MPI_COMM_WORLD);

【讨论】:

这个解决方案的缺点是什么?如果 MPI 知道数据的结构,它会以不同的方式处理数据吗?如果是,那么如何处理? 我不知道 MPI 如何发送 MPI 创建的结构类型,但一个合理的实现是发送整个结构,就像我使用仅用于计算偏移量的自定义类型一样。发送较小的片段会增加粒度,这可能会也可能不会加快速度,具体取决于每条消息的开销以及缓冲区大小等因素。

以上是关于C中的结构序列化并通过MPI传输的主要内容,如果未能解决你的问题,请参考以下文章

C ++ MPI创建并发送具有字段char [16]和整数的结构数组

MPI 发送自定义序列化对象(更通用的代码)

使用 MPI 集体通信发送 Struct

使用 MPI_Type_create_struct() 在 C 中传输包含动态数组的结构

Protocol buffer序列化及其在微信蓝牙协议中的应用

Protocol buffer序列化及其在微信蓝牙协议中的应用