Fortran + MPI:Gatherv 问题

Fortran + MPI: Issue with Gatherv

我正在尝试使用 Scatterv 分发二维数组,效果很好。但是对应的Gatherv操作报错:message truncated。谁能解释一下我做错了什么。

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

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

此处显示的代码中存在多个错误。

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) gather操作反过来也是如此。但是在那种情况下,除了一个(对于等级 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)

注意 sendcounts(mpi_rank+1)+1 的使用。 MPI 等级从 0 开始编号,而 Fortran 数组索引从 1 开始,除非另有说明。

此外,您不应使用 mpi_ 前缀来命名您自己的 subroutines/functions/modules/variables,以防止与真正的 MPI 符号发生名称冲突。