MPI中的派生数据类型

8
我正在学习Fortran中的BCAST数据类型,有一段代码从终端获取两个值并在每个进程上显示它们。对于类型为integer/integer和integer/real的组合value1/value2,这种方法可行,但对于integer/real*8的组合则失败。
代码如下:
use mpi
implicit none

integer :: ierror, pid, ncpu, root = 0

integer :: counts, newtype, extent
integer, dimension(2) :: oldtypes, blockcounts, offsets

type value
    integer :: value1 = 0
    real*8 :: value2
end type

type (value) input

call MPI_INIT(ierror)
call MPI_COMM_RANK(MPI_COMM_WORLD, pid, ierror)
call MPI_COMM_SIZE(MPI_COMM_WORLD, ncpu, ierror)

! setup of 1 MPI_INTEGER field: value1
offsets(1) = 0
oldtypes(1) = MPI_INTEGER
blockcounts(1) = 1

! setup of 1 MPI_REAL8 field: value2
call MPI_TYPE_EXTENT(MPI_INTEGER, extent, ierror)  !determine offset of MPI_INTEGER
offsets(2) = blockcounts(1)*extent                 !offset is 1 MPI_INTEGER extents
oldtypes(2) = MPI_REAL8
blockcounts(2) = 1

! define struct type and commit
counts = 2 !for MPI_INTEGER + MPI_REAL8
call MPI_TYPE_STRUCT(counts, blockcounts, offsets, & 
                     oldtypes, newtype, ierror)
call MPI_TYPE_COMMIT(newtype, ierror)

do while (input%value1 >= 0)
    if (pid == root) then
        read(*,*) input
        write(*,*) 'input was: ', input
    end if
    call MPI_BCAST(input, 1, newtype, &
                   root, MPI_COMM_WORLD, ierror)
    write(*,*), 'process ', pid, 'received: ', input
end do

call MPI_TYPE_FREE(newtype, ierror)
call MPI_FINALIZE(ierror)

可以通过更改相应的声明和oldtype来检查整数/整数和整数/实数是否正常工作。整数/实数*8的组合失败,例如输入-1 2.0会生成以下结果:
input was:           -1   2.0000000000000000     
process            0 received:           -1   2.0000000000000000     
process            1 received:           -1   0.0000000000000000     
process            2 received:           -1   0.0000000000000000     
process            3 received:           -1   0.0000000000000000

这个 类似问题的帖子表明使用 MPI_TYPE_EXTENT 不正确,因为可能存在未计算的额外填充。不幸的是,我还没有能够解决问题,希望这里有人能给我指点迷津。

提前感谢。

1个回答

10
你的基本想法是正确的——你已经创建了结构,但你假设双精度值紧随整数值之后存储,这通常是不正确的。Hristo的回答(你链接的)提供了一个好的C语言解决方案。
问题在于编译器通常会对你的数据结构字段进行对齐。大多数系统可以更快地读写内存中对齐的值,而非对齐访问则可能无法进行,或者速度较慢。通常要求对齐到元素大小;也就是说,8字节的双精度数必须对齐到8字节边界(也就是它的第一个字节的地址是0模8),而整数只需对齐到4字节。这几乎肯定意味着整数和双精度值之间有4个字节的填充。
在许多情况下,您可以劝说编译器放松此行为 - 在Fortran中,您还可以使用sequence关键字要求连续存储数据。无论哪种方式,从性能角度来看(这就是为什么您使用Fortran和MPI的原因),这几乎从来不是正确的做法,但它可用于与其他外部强制数据类型或格式的字节对字节兼容性。

鉴于出于性能原因可能会强制填充,您可以假定对齐并将其硬编码到程序中;但这也可能不是正确的做法;如果添加其他字段或更改实数的类型为4字节单精度数字等,则您的代码将再次出错。最好使用MPI_Get_address显式查找位置并自行计算正确的偏移量:

integer(kind=MPI_Address_kind) :: startloc, endloc    
integer :: counts, newtype
integer, dimension(2) :: oldtypes, blockcounts, offsets

type value
    integer :: value1 = 0
    double precision :: value2
end type

type (value) :: input

!...    

! setup of 1 MPI_INTEGER field: value1
call MPI_Get_address(input, startloc, ierror)
oldtypes(1) = MPI_INTEGER
blockcounts(1) = 1
call MPI_Get_address(input%value1, endloc, ierror)
offsets(1) = endloc - startloc

oldtypes(2) = MPI_DOUBLE_PRECISION
blockcounts(2) = 1
call MPI_Get_address(input%value2, endloc, ierror)
offsets(2) = endloc - startloc

if (pid == 0) then
    print *,'offsets are: ', offsets
endif

请注意,如果您有一个此类派生类型的数组,为了涵盖一个项目的最后一个元素和下一个元素之间的填充情况,您还需要明确测量它,并使用MPI_Type_create_resized设置该类型的成员之一的起始位置和下一个成员的起始位置之间的偏移量来设置类型的整体大小。

+1 谢谢您的澄清。我已经在尝试使用MPI_GET_ADDRESS,但还没有成功。您的示例非常好用! - nluigi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接