尝试在处理器之间传递 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=3
和 dimx=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)
我正在尝试将客户的 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=3
和 dimx=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)