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

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

这是结构:

struct car{
  int shifts;
  int topSpeed;
}myCar;

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

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

【问题讨论】:

    标签: c struct mpi


    【解决方案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 创建的结构类型,但一个合理的实现是发送整个结构,就像我使用仅用于计算偏移量的自定义类型一样。发送较小的片段会增加粒度,这可能会也可能不会加快速度,具体取决于每条消息的开销以及缓冲区大小等因素。
      猜你喜欢
      • 2019-10-23
      • 2016-04-04
      • 2016-09-17
      • 2012-10-23
      • 2011-06-18
      • 2014-02-22
      • 2013-08-29
      • 1970-01-01
      相关资源
      最近更新 更多