Fortran + MPI:Gatherv的问题

时间:2015-05-15 16:54:10

标签: fortran mpi

我正在尝试使用Scatterv分发2D数组,效果很好。但是,相应的Gatherv操作会出错:消息被截断。有人可以解释我做错了什么。

program scatterv
use mpi
implicit none

integer, allocatable, dimension(:,:) :: array
integer, allocatable, dimension(:) :: chunk
integer, allocatable, dimension(:) :: displacement
integer, allocatable, dimension(:) :: sendcounts
integer :: mpi_ierr, mpi_rank, mpi_size
integer, parameter :: kWidth=4

call MPI_INIT(mpi_ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, mpi_rank, mpi_ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, mpi_size, mpi_ierr)

if (mpi_rank == 0) then
    allocate(array(mpi_size, kWidth))
    allocate(displacement(mpi_size))
    allocate(sendcounts(mpi_size))
    displacement = (/0, 0, 0, 0, 0, 0, 0, 0, 0, 0/)
    sendcounts = (/2, 10, 5, 8, 5, 2, 2, 2, 2, 2/)
endif

allocate(chunk(mpi_size))

call MPI_SCATTERV(array, sendcounts, displacement, MPI_INTEGER, chunk, mpi_size, MPI_INTEGER, 0, MPI_COMM_WORLD, mpi_ierr)

...

call MPI_GATHERV(chunk, mpi_size, MPI_INTEGER, array, sendcounts, displacement, MPI_INTEGER, 0, MPI_COMM_WORLD, mpi_ierr)

if (mpi_rank == 0) then
    deallocate(array)
    deallocate(displacement)
end if
deallocate(chunk)

call MPI_FINALIZE(mpi_ierr)
end program scatterv

2 个答案:

答案 0 :(得分:3)

此处提供的代码中存在多个错误。

1)所有的位移是相等的:

if (mpi_rank == 0) then
    ...
    displacement = (/0, 0, 0, 0, 0, 0, 0, 0, 0, 0/)
    sendcounts = (/2, 10, 5, 8, 5, 2, 2, 2, 2, 2/)
endif

MPI标准要求发送缓冲区中的任何位置都不应被读取两次,并且接收缓冲区中的任何位置都不应写入两次。换句话说,所有块必须是不相交的。仅当相应的发送计数为0(零)时,才允许位移相等。

由于性能原因,某些(如果不是大多数)MPI库不强制执行此条件。它可能有效,可能不起作用,全部取决于用于传输数据的设备。即使它有效,它仍然是不正确的MPI。

2)MPI_SCATTERV中的接收计数与块大小不匹配:

call MPI_COMM_SIZE(MPI_COMM_WORLD, mpi_size, mpi_ierr)
...
sendcounts = (/2, 10, 5, 8, 5, 2, 2, 2, 2, 2/)
...
call MPI_SCATTERV(array, sendcounts, displacement, MPI_INTEGER, &
                  chunk, mpi_size, MPI_INTEGER, &
                  0, MPI_COMM_WORLD, mpi_ierr)

对于点对点操作,可以提供大于消息实际占用的缓冲区,而集合操作则不是这样 - 发送到进程的数据量必须匹配进程指定的接收缓冲区的大小。有些实现适用于较大的缓冲区,但依赖它的程序不正确。

分散操作的唯一原因是你有10个MPI进程(根据数组初始化程序的大小判断),最大的块大小也是10。

3)这同样适用于收集操作。但在这种情况下,除了一个(对于等级1),所有发送计数都大于预期的块大小。

程序的更正版本应如下所示:

call MPI_INIT(mpi_ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, mpi_rank, mpi_ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, mpi_size, mpi_ierr)

allocate(sendcounts(mpi_size))
sendcounts = (/2, 10, 5, 8, 5, 2, 2, 2, 2, 2/)

if (mpi_rank == 0) then
    allocate(array(mpi_size, kWidth))
    allocate(displacement(mpi_size))
    displacement = (/0, 2, 12, 17, 25, 27, 29, 31, 33, 35/)
endif

allocate(chunk(mpi_size))

call MPI_SCATTERV(array, sendcounts, displacement, MPI_INTEGER, &
                  chunk, sendcounts(mpi_rank+1), MPI_INTEGER, &
                  0, MPI_COMM_WORLD, mpi_ierr)

...

call MPI_GATHERV(chunk, sendcounts(mpi_rank+1), MPI_INTEGER, &
                 array, sendcounts, displacement, MPI_INTEGER, &
                 0, MPI_COMM_WORLD, mpi_ierr)

if (mpi_rank == 0) then
    deallocate(array)
    deallocate(displacement)
end if

deallocate(chunk)
deallocate(sendcounts)

call MPI_FINALIZE(mpi_ierr)

请注意+1sendcounts(mpi_rank+1)的使用情况。除非另有说明,否则MPI排名从0开始编号,而Fortran数组索引从1开始。

此外,您不应使用mpi_前缀来命名您自己的子例程/函数/模块/变量,以防止与真正的MPI符号发生名称冲突。

答案 1 :(得分:2)

问题是发送的数据量大于根告诉MPI预期的数据量。您创建了一个名为sendcounts的数组,其中包含一些计数,根进程将使用这些计数将数组中的空格分配给不同的排名,但每个进程都发送mpi_size,这可能比某些sendcounts更大(例如2)。您需要确保数字匹配。您可以找到示例代码here