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

Posted

技术标签:

【中文标题】具有动态分配成员的动态分配结构的 MPI 派生数据类型【英文标题】:MPI derived datatype for dynamically allocated structs with dynamically allocated member 【发布时间】:2017-02-07 21:04:40 【问题描述】:

有一个动态分配的结构体:

TYPE Struct    
    INTEGER :: N
    REAL*8 :: A
    REAL*8,ALLOCATABLE :: B(:)
END TYPE Struct

它有一个动态分配的成员:B(:)

当我尝试使用 MPI_TYPE_CREATE_STRUCT 为此类 Struct 创建派生数据类型时,不同的 CPU 会创建不一致的派生数据类型。 这是因为相对于第一个成员 Struct%N,Struct%B(:) 可能位于不同的内存位置,在不同的 CPU 上。

那么 MPI_SEND(Struct,...) 不会成功...

那么,如果我真的想使用 MPI 派生的数据类型发送这个 Struct,如何解决这样的问题?

还是禁止这种派生数据类型?

【问题讨论】:

很奇怪你是怎么写这整个问题的,而且你没有提到你正在使用的语言 Fortran。好吧,我为你标记了它。 你见过***.com/questions/2258759/…和***.com/questions/4273253/…吗? 对不起标志...我刚才看到这些链接,它们没有解决我遇到的问题,我的意思是:使用 REAL*8 :: B (2) 而不是可分配的一个是可以的,没问题!问题显示当我使用 REAL*8,ALLOCATABLE :: B(:) @JohnZwinck real*8 不是有效的 Fortran 并且从来不是任何 ISO Fortran 标准的一部分。请使用selected_real_kind 或内部模块ISO_Fortran_env 中的命名参数常量以可移植的方式控制精度。 【参考方案1】:

要发送该结构的一个元素,只需使用MPI_TYPE_CREATE_STRUCT 创建结构化数据类型proceed as usual。根据程序的堆和堆栈的相对位置,B(1) 相对于N 的偏移量最终可能是一个巨大的正数或负数,但这在大多数 Unix 上都没有问题平台和数字应该在INTEGER(KIND=MPI_ADDRESS_KIND)的范围内。

重要提示:结构的单独实例很可能具有不同的 B 相对于 N 的偏移量,因此 MPI 数据类型只能用于发送用于发送的特定记录在数据类型的构造过程中获取偏移量。

当我尝试使用 MPI_TYPE_CREATE_STRUCT 为此类 Struct 创建派生数据类型时,不同的 CPU 会创建不一致的派生数据类型。这是因为相对于第一个成员 Struct%N,Struct%B(:) 可能位于不同的内存位置,在不同的 CPU 上。

这不是问题。通信操作双方的 MPI 数据类型只能是全等的,这意味着它们应该由相同顺序的相同基本数据类型组成。 每个元素的偏移量无关。 换句话说,只要发送者和接收者在MPI_TYPE_CREATE_STRUCT调用中指定相同类型和数量的数据元素,程序就会正常运行。

要发送多个元素,事情会变得有点复杂。有两种解决方案:

使用MPI_PACK 序列化发送方的数据,使用MPI_UNPACK 反序列化接收方的数据。由于打包和解包需要额外的缓冲区空间,这会使程序的内存需求增加一倍。

为每条记录创建一个单独的 MPI 结构数据类型,然后创建一个组合所有记录的结构数据类型。下面是一个示例,说明如何发送包含两个此类结构的数组,一个在 B 中包含 10 个元素,一个包含 20 个元素:

TYPE(Struct) :: Structs(2)

ALLOCATE(Structs(1)%B(10))
ALLOCATE(Structs(2)%B(20))

! (1) Create a separate structure datatype for each record
DO i=1,2
  CALL MPI_GET_ADDRESS(Structs(i)%N,    POS_(1), IError)
  CALL MPI_GET_ADDRESS(Structs(i)%A,    POS_(2), IError)
  CALL MPI_GET_ADDRESS(Structs(i)%B(1), POS_(3), IError)
  Offsets = POS_ - POS_(1)

  Types(1) = MPI_INTEGER
  Types(2) = MPI_REAL8
  Types(3) = MPI_REAL8

  Blocks(1) = 1
  Blocks(2) = 1
  Blocks(3) = i * 10

  CALL MPI_TYPE_CREATE_STRUCT(3, Blocks, Offsets, Types, Elem_Type(i), IError)
END DO

! (2) Create a structure of structures that describes the whole array
CALL MPI_GET_ADDRESS(Structs(1)%N, POS_(1), IError)
CALL MPI_GET_ADDRESS(Structs(2)%N, POS_(2), IError)
Offsets = POS_ - POS_(1)

Types(1) = Elem_Type(1)
Types(2) = Elem_Type(2)

Blocks(1) = 1
Blocks(2) = 1

CALL MPI_TYPE_CREATE_STRUCT(2, Blocks, Offsets, Types, TwoElem_Type, IError)
CALL MPI_TYPE_COMMIT(TwoElem_Type, IError)

! (2.1) Free the intermediate datatypes
DO i=1,2
  CALL MPI_TYPE_FREE(Elem_Type(i), IError)
END DO

! (3) Send the array
CALL MPI_SEND(Structs(1)%N, 1, TwoElem_Type, ...)

请注意,尽管构造 MPI 数据类型是一项相对便宜的操作,但您不应使用上述过程发送例如结构化类型的 1000000 个实例。此外,MPI 数据类型描述符存在于库管理的内存中,及时释放不再需要的数据类型很重要。

【讨论】:

如果数组中结构的数量变化很大怎么办?每次我们都需要定义一个新的数据类型,例如TwoElem_TypeMillionElem_Type等。 是的,不幸的是,MPI 主要设计用于“线性”类型,即按照某种重复模式顺序存储在内存中的类型。不直接支持将数据存储在内存周围并通过指针引用的分支类型,并且必须破解他们的方式。尽管如此,对于非常大的数据对象,构造数据类型比通过网络发送数据要便宜得多,所以这应该不是什么大问题。

以上是关于具有动态分配成员的动态分配结构的 MPI 派生数据类型的主要内容,如果未能解决你的问题,请参考以下文章

将结构成员指针分配给另一个动态内存分配的指针是不是安全?

mpi中的darray和子数组有啥区别?

数组的动态分配

为具有双指针的结构内的动态结构数组分配内存**

C动态成员结构

CUDA中常量内存的动态分配