在mpi中使用可分配数组发送派生类型数据时出现seg错误

时间:2015-07-24 16:16:25

标签: fortran mpi allocatable-array

我正在尝试在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

它似乎得到负面的地址抵消。请帮忙。 感谢。

1 个答案:

答案 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_WAITMPI_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