尝试在处理器之间传递 MPI 派生类型(失败)

Trying to pass MPI derived types between processors (and failing)

我正在尝试将客户的 Fortran 代码与 MPI 并行化。 f 是维度为 f(dimx,dimy,dimz,dimf) 的 4 字节实数数组。我需要各种进程来处理数组第一维的不同部分。 (我宁愿从最后一个开始,但这不取决于我。)所以我定义了一个派生类型 mpi_x_inteface 就像这样

call mpi_type_vector(dimy*dimz*dimf, 1, dimx, MPI_REAL,  &
                     mpi_x_interface, mpi_err)
call mpi_type_commit(mpi_x_interface, mpi_err)

我的意图是单个 mpi_x_interface 将在某个给定的第一个索引 "i" 处包含 'f' 中的所有数据。也就是说,对于给定的 i,它应该包含 f(i,:,:,:)。 (请注意,在游戏的这个阶段,所有 proc 都有 f 的完整副本。我打算最终在 proc 之间拆分 f,除了我希望 proc 0 有一个完整副本聚会的目的。)

ptsinproc 是一个数组,其中包含每个 proc 处理的 "i" 个索引的数量。 x_slab_displs 是每个过程从数组开始的位移。对于我正在测试的两个过程,它们是 ptsinproc=(/61,60/)x_slab_displs=(/0,61/)myminpt 是一个简单的整数,给出每个过程中处理的最小索引。

所以现在我想将所有 f 收集到 proc 0 并且我 运行

    if (myrank == 0) then
      call mpi_gatherv(MPI_IN_PLACE, ptsinproc(myrank),
 +                     mpi_x_interface, f(1,1,1,1), ptsinproc,
 +                     x_slab_displs, mpi_x_interface, 0,
 +                     mpi_comm_world, mpi_err)
    else
      call mpi_gatherv(f(myminpt,1,1,1), ptsinproc(myrank),
 +                     mpi_x_interface, f(1,1,1,1), ptsinproc,
 +                     x_slab_displs, mpi_x_interface, 0,
 +                     mpi_comm_world, mpi_err)
    endif

我最多可以发送一个 "slab" 这样的。如果我尝试将整个 60 "slabs" 从 proc 1 发送到 proc 0,我会由于 "invalid memory reference" 而出现段错误。顺便说一句,即使我发送了那个单板,数据也会出现在错误的地方。

我已经检查了所有明显的东西,比如确保 myrank 和 ptsinproc 以及 x_slab_dislps 是它们在所有过程中应该是什么。我研究了 "size" 和 "extent" 之间的区别等等,但无济于事。我已经无计可施了。我只是不明白我做错了什么。有人可能还记得几个月前我问过一个类似(但不同!)的问题。我承认我只是不明白。感谢您的耐心等待。

首先,我只想说您 运行 遇到这么多问题的原因是因为您试图拆分第一个(最快的)轴。根本不推荐这样做,因为按原样打包 mpi_x_interface 需要大量不连续的内存访问。我们正在谈论性能的巨大损失。

跨 MPI 进程拆分最慢的轴是一个更好的策略。我强烈建议转置您的 4D 矩阵,以便 x 轴在最后。

现在解决您的实际问题...

派生数据类型

正如您推断的那样,一个问题是派生数据类型的大小和范围可能不正确。让我们稍微简化一下你的问题,这样我就可以画一幅画了。说 dimy*dimz*dimf=3dimx=4。按原样,您的数据类型 mpi_x_interface 描述了内存中的以下数据:

| X |   |   |   | X |   |   |   | X |   |   |   |

即每 4 次 MPI_REAL,总共 3 次。既然这就是您想要的,到目前为止还不错:变量的大小是正确的。但是,如果您尝试发送 "the next" mpi_x_interface,您会看到 MPI 的实现将从内存中的下一个点开始(在您的情况下尚未分配),并抛出一个 "invalid memory access"对你:

                                             tries to access and bombs
                                                 vvv
| X |   |   |   | X |   |   |   | X |   |   |   | Y |   |   |   | Y | ...

作为数据类型的一部分,您需要告诉 MPI 的是 "the next" mpi_x_interface 仅从 1 real 开始进入数组。这是通过调用 MPI_Type_create_resized() 重新定义派生数据类型的 "extent" 来实现的。在你的情况下,你需要写

integer :: mpi_x_interface, mpi_x_interface_resized
integer, parameter :: SIZEOF_REAL = 4 ! or whatever f actually is

call mpi_type_vector(dimy*dimz*dimf, 1, dimx, MPI_REAL,  &
                 mpi_x_interface, mpi_err)
call mpi_type_create_resized(mpi_x_interface, 0, 1*SIZEOF_REAL, &
                             mpi_x_interface_resized, mpi_err)
call mpi_type_commit(mpi_x_interface_resized, mpi_err)

然后,调用 "the next" 3 mpi_x_interface_resized 将导致:

| X | Y | Z | A | X | Y | Z | A | X | Y | Z | A |

符合预期。

MPI_Gatherv

请注意,现在您已经正确定义了数据类型的范围,使用数据类型的偏移量调用 mpi_gatherv 现在应该可以按预期工作。

就我个人而言,我认为没有必要用 MPI_IN_PLACE 尝试一些花哨的逻辑来进行集体操作。您可以简单地在 myrank==0 上设置 myminpt=1。然后你可以在每个等级上调用:

   call mpi_gatherv(f(myminpt,1,1,1), ptsinproc(myrank),
+                     mpi_x_interface_resized, f, ptsinproc,
+                     x_slab_displs, mpi_x_interface_resized, 0,
+                     mpi_comm_world, mpi_err)