我正在尝试在mpi广告中发送带有可分配数组的派生类型数据,但却出现了段错误。
program test_type
use mpi
implicit none
type mytype
real,allocatable::x(:)
integer::a
end type mytype
type(mytype),allocatable::y(:)
type(mytype)::z
integer::n,i,ierr,myid,ntasks,status,request
integer :: datatype, oldtypes(2), blockcounts(2)
integer(KIND=MPI_ADDRESS_KIND) :: offsets(2)
call mpi_init(ierr)
call mpi_comm_rank(mpi_comm_world,myid,ierr)
call mpi_comm_size(mpi_comm_world,ntasks,ierr)
n=2
allocate(z%x(n))
if(myid==0)then
allocate(y(ntasks-1))
do i=1,ntasks-1
allocate(y(i)%x(n))
enddo
else
call random_number(z%x)
z%a=myid
write(0,*) "z in process", myid, z%x, z%a
endif
call mpi_get_address(z%x,offsets(1),ierr)
call mpi_get_address(z%a,offsets(2),ierr)
offsets=offsets-offsets(1)
oldtypes=(/ mpi_real,mpi_integer /)
blockcounts=(/ n,1 /)
write(0,*) "before commit",myid,offsets,blockcounts,oldtypes
call mpi_type_create_struct(2,blockcounts,offsets,oldtypes,datatype,ierr)
call mpi_type_commit(datatype, ierr)
write(0,*) "after commit",myid,datatype, ierr
if(myid==0) then
do i=1,ntasks-1
call mpi_irecv(y(i),1,datatype,1,0,mpi_comm_world,request,ierr)
write(0,*) "received", y(i)%x,y(i)%a
enddo
else
call mpi_isend(z,1,datatype,0,0,mpi_comm_world,request,ierr)
write(0,*) "sent"
write(0,*) myid, z%x, z%a
end if
call mpi_finalize(ierr)
end program
这就是我用2个流程打印出的内容:
before commit 0 0 -14898056
2 1 13 7
after commit 0 73 0
z in process 1 3.9208680E-07 2.5480442E-02 1
before commit 1 0 -491689432
2 1 13 7
after commit 1 73 0
received 0.0000000E+00 0.0000000E+00 0
forrtl: severe (174): SIGSEGV, segmentation fault occurred
它似乎得到负面的地址抵消。请帮忙。 感谢。
答案 0 :(得分:2)
此代码存在多个问题。
具有大多数Fortran编译器的可分配数组类似于C / C ++中的指针:数组名称背后的真实对象是保存指向已分配数据的指针。该数据通常在堆上分配,并且可以在进程的虚拟地址空间中的任何位置,这解释了负偏移。顺便说一句,负偏移在MPI数据类型中完全可以接受(这就是MPI_ADDRESS_KIND
指定签名整数种类的原因),所以这里没有大问题。
更大的问题是动态分配事物之间的偏移量通常随每次分配而变化。你可以检查:
ADDR(y(1)%x) - ADDR(y(1)%a)
与
完全不同ADDR(y(i)%x) - ADDR(y(i)%a), for i = 2..ntasks-1
(ADDR
这里只是MPI_GET_ADDRESS
返回的对象地址的符号表示法
即使它发生偏移匹配i
的某些值,这也是一个巧合而不是规则。
这导致以下内容:使用z
变量的偏移量构造的类型不能用于发送y
数组的元素。要解决此问题,只需删除mytype%x
的可分配属性(如果可能的话)(例如,事先知道n
)。
另一个适用于ntasks
小值的选项是定义与y
数组的元素数一样多的MPI数据类型。然后使用基于datatype(i)
和y(i)%x
偏移的y(i)%a
发送y(i)
。
更严重的问题是您正在使用非阻塞MPI操作,并且在访问数据缓冲区之前永远不会等待它们完成。这段代码简直无法工作:
do i=1,ntasks-1
call mpi_irecv(y(i),1,datatype,1,0,mpi_comm_world,request,ierr)
write(0,*) "received", y(i)%x,y(i)%a
enddo
调用MPI_IRECV
启动异步接收操作。在WRITE
运算符执行时,操作可能仍在进行中,因此正在访问完全随机的数据(某些内存分配器实际上可能在调试模式下将数据归零)。在MPI_WAIT
和MPI_ISEND
来电之间插入WRITE
来电或使用屏蔽接收MPI_RECV
。
使用非阻塞发送呼叫MPI_ISEND
存在类似问题。由于您永远不会等待请求的完成或对其进行测试,因此允许MPI库无限期地推迟操作的实际进程,并且发送可能永远不会发生。同样,由于在您的案例中绝对没有理由使用非阻止发送,请将MPI_ISEND
替换为MPI_SEND
。
最后但并非最不重要的是,排名0仅接收来自排名1的消息:
call mpi_irecv(y(i),1,datatype,1,0,mpi_comm_world,request,ierr)
^^^
同时,所有其他进程都发送到排名0.因此,只有在运行两个MPI进程时,您的程序才会起作用。您可能希望使用1
替换接收呼叫中带下划线的i
。