我必须在FORTRAN 90中发送和接收(MPI)一大块多维数组。该行
MPI_Send(x(2:5,6:8,1),12,MPI_Real,....)
根据Gropp,Lusk和Skjellum的书“Using MPI ...”,不应该使用。做这个的最好方式是什么?我是否必须创建一个临时数组并发送它或使用MPI_Type_Create_Subarray
或类似的东西?
答案 0 :(得分:2)
不使用带MPI_SEND
的数组部分的原因是编译器必须使用某些 MPI实现创建临时副本。这是因为Fortran只能将数组部分正确地传递给具有显式接口的子程序,并且必须在所有其他情况下生成临时的“扁平”副本,通常在调用子例程的堆栈上。不幸的是,在TR 29113扩展到F2008之前的Fortran中,没有办法声明采用变量类型参数的子例程,而MPI实现通常采用语言黑客,例如: MPI_Send
完全在C中实现,并且依赖于Fortran始终将数据作为指针传递。
一些MPI库通过为MPI_SEND
生成大量重载来解决此问题:
INTEGER
INTEGER
然后对INTEGER
,CHARACTER
,LOGICAL
等重复同样的操作。这仍然是一个黑客攻击,因为它不包括通过用户定义类型的情况。此外,它使C实现变得非常复杂,因为它现在必须理解Fortran数组描述符,它们是特定于编译器的。
幸运的是,时代正在发生变化。 Fortran 2008的TR 29113扩展包括两个新功能:
DOUBLE PRECISION
TYPE(*)
两者的组合,即DIMENSION(..)
,描述了一个既可以是不同类型又具有任何维度的论证。这已经在MPI-3中的新TYPE(*), DIMENSION(..), INTENT(IN) :: buf
接口中得到利用。
非阻塞调用在Fortran中提出的问题超出了Alexander Vogt所描述的范围。原因是Fortran没有抑制编译器优化的概念(即Fortran中没有mpi_f08
个关键字)。以下代码可能无法按预期运行:
volatile
有人可能会在调用INTEGER :: data
data = 10
CALL MPI_IRECV(data, 1, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, req, ierr)
! data is not used here
! ...
CALL MPI_WAIT(req, MPI_STATUS_IGNORE, ierr)
! data is used here
MPI_WAIT
之后包含从0级收到的值,但这种情况可能并非如此。原因是编译器无法知道data
在data
返回后可能异步更改,因此将其值保存在寄存器中。这就是为什么在Fortran中通常认为非阻塞MPI调用是危险的。
TR 29113也使用MPI_IRECV
属性解决了第二个问题。如果您查看ASYNCHRONOUS
mpi_f08
的定义,其MPI_IRECV
参数将声明为:
buf
即使TYPE(*), DIMENSION(..), INTENT(OUT), ASYNCHRONOUS :: buf
是标量参数,即没有创建临时副本,符合TR 29113的编译器也不会求助于缓冲区参数的寄存器优化。
答案 1 :(得分:0)
MPI_Send
总是阻塞,但可能会选择异步发送数据。来自here:
在您使用发送缓冲区之前,MPI_Send不会返回。
当涉及非连续数组时,非阻塞通信(如MPI_Send
)可能会对Fortran造成问题。然后,编译器为虚拟变量创建一个临时数组,并将其传递给子例程。子例程完成后,编译器可以自由释放该副本的内存。
只要你使用阻塞通信(MPI_Send
)就没问题,因为当子程序返回时,就会发送消息。但是,对于非阻塞通信(MPI_Isend
),临时数组是发送缓冲区,子例程在发送之前返回。
因此可能会发生,MPI将从不再保留有效数据的内存位置发送数据。
因此,您自己创建一个副本(以便您的发送缓冲区在内存中是连续的),或者您创建一个子数组(即告诉MPI内存中您要发送的元素的地址)。还有其他替代方案,例如MPI_Pack
,但我没有经验。
哪种方式更快?嗯,这取决于:
有关详细说明和更多选项,请参阅here。