如何使用 MPI 发送正确数量的派生类型对象?
How do I use MPI to send the correct number of derived-type objects?
对 MPI 有一些经验,但对派生类型等更高级的方面没有经验,这正是我的问题所涉及的。
我正在处理的代码有几个维度为 (-1:nx+2,-1:ny+2,-1:nz+2)
的数组。为了清楚起见,每个进程都有自己的值nx
、ny
和nz
。阵列之间存在重叠。例如,一个过程中的 x(:,:,-1:2)
将代表与过程中的 x(:,:,nz-1:nz+2)
相同的信息,只是 "below" 它。
已定义派生的 cell_zface
类型:
idir = 3
sizes = (/nx_glb, ny_glb, nz_glb/) !These nums are the same for all procs.
subsizes = (/nx, ny, 2/)
mpitype = MPI_DATATYPE_NULL
CALL MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, &
MPI_ORDER_FORTRAN, mpireal, mpitype, errcode)
CALL MPI_TYPE_COMMIT(mpitype, errcode)
cell_zface = mpitype
现在,此派生类型已成功用于多个 MPI_SENDRECV
调用。例如
CALL MPI_SENDRECV( &
x(-1,-1, 1), 1, cell_zface, proc_z_min, tag, &
x(-1,-1,nz+1), 1, cell_zface, proc_z_max, tag, &
comm, status, errcode)
据我了解,此调用在 proc 之间发送和接收数组的两个 "horizontal" 切片(即 x-y 切片)。
我想做一些不同的事情,即发送四个 "horizontal" 切片。所以我尝试
call mpi_send(x(-1,-1,nz-1), 2, cell_zface, &
proc_z_max, rank, comm, mpierr)
附带接收。
最后,我的问题是:代码可以运行,但出错了。 AFAICT,这只发送两个水平切片,即使我使用“2”而不是“1”作为计数参数。我可以通过两次调用 mpi_send
:
来解决这个问题
call mpi_send(x(-1,-1,nz-1), 1, cell_zface, &
proc_z_max, rank, comm, mpierr)
call mpi_send(x(-1,-1,nz+1), 1, cell_zface, &
proc_z_max, rank, comm, mpierr)
附带接收,但这肯定不漂亮。
那么,为什么 mpi_send
只发送两个水平切片,即使我将计数参数设置为“2”?有没有一种干净的方法来做我想做的事情?
可以说,每个 MPI 数据类型都有两种大小。一个是真实大小,即存储数据类型引用的所有重要数据所需的内存量。可以将其视为该数据类型的元素占用的实际消息中 space 的数量。
另一种尺寸就是所谓的范围。 MPI 中的每个数据类型都是以下类型指令的集合:"go to offset dispi from the provided buffer location and read/write an element of basic type typei"。所有 (typei, dispi) 对的集合称为数据类型的类型映射.最小偏移量称为下限,最大偏移量+该偏移量处基本类型的大小+所需的任何填充称为上限。数据类型的范围是上限和下限之间的差异,并给出最短连续内存区域的大小,其中包括数据类型访问的所有位置。
由于 MPI 规定在任何通信操作期间不得多次读取或写入内存位置,因此类型映射中的对必须引用不相交的位置。因此,数据类型的真实范围总是大于或等于它的大小。
MPI 在访问该数据类型的连续元素时使用该数据类型的范围。以下语句:
MPI_SEND(buf, n, dtype, ...)
结果:
- MPI 从位置
buf
获取类型 dtype
的一个元素,遵循编码为 dtype
; 类型映射的规则
- MPI 从位置
buf + extent(dtype)
; 开始获取下一个元素
- ...
- MPI 采用从位置
buf + (n-1)*extent(dtype)
开始的第 n
个元素。
MPI_INTEGER
、MPI_REAL
等原始数据类型的范围与基本类型(INTEGER
、REAL
等)的大小相匹配。体系结构要求的任何填充,这使得可以通过简单地指定元素的数量来发送基本类型的数组。
现在,回到你的案例。您正在创建一个覆盖 nx_glb x ny_glb x nz_glb
数组中的 nx x ny x 2
子数组的数据类型。该数据类型的大小确实是 mpireal
大小的 nx * ny * 2
倍,但范围实际上是 mpireal
范围的 nx_glb * ny_glb * nz_glb
倍。换句话说:
MPI_SEND(buf, 2, cell_zface, ...)
不会从 buf
的大数组中提取两个连续的 nx x ny x 2
slab。相反,它将从大小为 nx_glb x ny_glb x nz_glb
的两个连续数组中的每一个中提取一个 slab,从位置 (startx, starty 开始,在每个数组中开始z)。如果你的程序在 运行 时没有出现段错误,那你就走运了。
棘手的部分来了。 MPI 允许通过人为设置下限和上限的值来为每个数据类型提供一个假范围(这就是我之前定义的范围 "true" 的原因)。这样做不会影响数据类型或其类型映射的大小(即 MPI 仍然使用相同的偏移量并操作相同基本类型的元素),但会影响 MPI 在访问给定数据类型的连续元素时在内存中的步幅。早些时候,设置范围是通过 "sandwiching" 特殊伪类型 MPI_LB
和 MPI_UB
元素之间的结构中的数据类型完成的。自 MPI-2 以来,MPI_TYPE_CREATE_RESIZED
用于实现相同的目的。
integer(kind=MPI_ADDRESS_KIND) :: lb, extent
integer :: newtype
! First obtain the extent of the old type used to construct cell_zface
call MPI_TYPE_GET_EXTENT(mpireal, lb, extent, errcode)
! Adjust the extent of cell_zface
extent = (nx_glb * ny_glb * subsizes(3)) * extent
call MPI_TYPE_CREATE_RESIZED(cell_zface, lb, extent, newtype, errcode)
call MPI_TYPE_COMMIT(newtype, errcode)
! Get rid of the previous type
call MPI_TYPE_FREE(cell_zface, errcode)
cell_zface = newtype
您现在可以使用 cell_zface
发送多个连续的 slab。
另一种可能更简单的方法是在调用 MPI_TYPE_CREATE_SUBARRAY
:
时将数组的第 3 维大小设置为等于子数组的第 3 维大小
idir = 3
subsizes = (/nx, ny, 2/)
sizes = (/nx_glb, ny_glb, subsizes(3)/) !These nums are the same for all procs.
mpitype = MPI_DATATYPE_NULL
CALL MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, &
MPI_ORDER_FORTRAN, mpireal, mpitype, errcode)
CALL MPI_TYPE_COMMIT(mpitype, errcode)
cell_zface = mpitype
在这两种情况下,我都假设 starts(3)
等于 0
。
对 MPI 有一些经验,但对派生类型等更高级的方面没有经验,这正是我的问题所涉及的。
我正在处理的代码有几个维度为 (-1:nx+2,-1:ny+2,-1:nz+2)
的数组。为了清楚起见,每个进程都有自己的值nx
、ny
和nz
。阵列之间存在重叠。例如,一个过程中的 x(:,:,-1:2)
将代表与过程中的 x(:,:,nz-1:nz+2)
相同的信息,只是 "below" 它。
已定义派生的 cell_zface
类型:
idir = 3
sizes = (/nx_glb, ny_glb, nz_glb/) !These nums are the same for all procs.
subsizes = (/nx, ny, 2/)
mpitype = MPI_DATATYPE_NULL
CALL MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, &
MPI_ORDER_FORTRAN, mpireal, mpitype, errcode)
CALL MPI_TYPE_COMMIT(mpitype, errcode)
cell_zface = mpitype
现在,此派生类型已成功用于多个 MPI_SENDRECV
调用。例如
CALL MPI_SENDRECV( &
x(-1,-1, 1), 1, cell_zface, proc_z_min, tag, &
x(-1,-1,nz+1), 1, cell_zface, proc_z_max, tag, &
comm, status, errcode)
据我了解,此调用在 proc 之间发送和接收数组的两个 "horizontal" 切片(即 x-y 切片)。
我想做一些不同的事情,即发送四个 "horizontal" 切片。所以我尝试
call mpi_send(x(-1,-1,nz-1), 2, cell_zface, &
proc_z_max, rank, comm, mpierr)
附带接收。
最后,我的问题是:代码可以运行,但出错了。 AFAICT,这只发送两个水平切片,即使我使用“2”而不是“1”作为计数参数。我可以通过两次调用 mpi_send
:
call mpi_send(x(-1,-1,nz-1), 1, cell_zface, &
proc_z_max, rank, comm, mpierr)
call mpi_send(x(-1,-1,nz+1), 1, cell_zface, &
proc_z_max, rank, comm, mpierr)
附带接收,但这肯定不漂亮。
那么,为什么 mpi_send
只发送两个水平切片,即使我将计数参数设置为“2”?有没有一种干净的方法来做我想做的事情?
可以说,每个 MPI 数据类型都有两种大小。一个是真实大小,即存储数据类型引用的所有重要数据所需的内存量。可以将其视为该数据类型的元素占用的实际消息中 space 的数量。
另一种尺寸就是所谓的范围。 MPI 中的每个数据类型都是以下类型指令的集合:"go to offset dispi from the provided buffer location and read/write an element of basic type typei"。所有 (typei, dispi) 对的集合称为数据类型的类型映射.最小偏移量称为下限,最大偏移量+该偏移量处基本类型的大小+所需的任何填充称为上限。数据类型的范围是上限和下限之间的差异,并给出最短连续内存区域的大小,其中包括数据类型访问的所有位置。
由于 MPI 规定在任何通信操作期间不得多次读取或写入内存位置,因此类型映射中的对必须引用不相交的位置。因此,数据类型的真实范围总是大于或等于它的大小。
MPI 在访问该数据类型的连续元素时使用该数据类型的范围。以下语句:
MPI_SEND(buf, n, dtype, ...)
结果:
- MPI 从位置
buf
获取类型dtype
的一个元素,遵循编码为dtype
; 类型映射的规则
- MPI 从位置
buf + extent(dtype)
; 开始获取下一个元素
- ...
- MPI 采用从位置
buf + (n-1)*extent(dtype)
开始的第n
个元素。
MPI_INTEGER
、MPI_REAL
等原始数据类型的范围与基本类型(INTEGER
、REAL
等)的大小相匹配。体系结构要求的任何填充,这使得可以通过简单地指定元素的数量来发送基本类型的数组。
现在,回到你的案例。您正在创建一个覆盖 nx_glb x ny_glb x nz_glb
数组中的 nx x ny x 2
子数组的数据类型。该数据类型的大小确实是 mpireal
大小的 nx * ny * 2
倍,但范围实际上是 mpireal
范围的 nx_glb * ny_glb * nz_glb
倍。换句话说:
MPI_SEND(buf, 2, cell_zface, ...)
不会从 buf
的大数组中提取两个连续的 nx x ny x 2
slab。相反,它将从大小为 nx_glb x ny_glb x nz_glb
的两个连续数组中的每一个中提取一个 slab,从位置 (startx, starty 开始,在每个数组中开始z)。如果你的程序在 运行 时没有出现段错误,那你就走运了。
棘手的部分来了。 MPI 允许通过人为设置下限和上限的值来为每个数据类型提供一个假范围(这就是我之前定义的范围 "true" 的原因)。这样做不会影响数据类型或其类型映射的大小(即 MPI 仍然使用相同的偏移量并操作相同基本类型的元素),但会影响 MPI 在访问给定数据类型的连续元素时在内存中的步幅。早些时候,设置范围是通过 "sandwiching" 特殊伪类型 MPI_LB
和 MPI_UB
元素之间的结构中的数据类型完成的。自 MPI-2 以来,MPI_TYPE_CREATE_RESIZED
用于实现相同的目的。
integer(kind=MPI_ADDRESS_KIND) :: lb, extent
integer :: newtype
! First obtain the extent of the old type used to construct cell_zface
call MPI_TYPE_GET_EXTENT(mpireal, lb, extent, errcode)
! Adjust the extent of cell_zface
extent = (nx_glb * ny_glb * subsizes(3)) * extent
call MPI_TYPE_CREATE_RESIZED(cell_zface, lb, extent, newtype, errcode)
call MPI_TYPE_COMMIT(newtype, errcode)
! Get rid of the previous type
call MPI_TYPE_FREE(cell_zface, errcode)
cell_zface = newtype
您现在可以使用 cell_zface
发送多个连续的 slab。
另一种可能更简单的方法是在调用 MPI_TYPE_CREATE_SUBARRAY
:
idir = 3
subsizes = (/nx, ny, 2/)
sizes = (/nx_glb, ny_glb, subsizes(3)/) !These nums are the same for all procs.
mpitype = MPI_DATATYPE_NULL
CALL MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, &
MPI_ORDER_FORTRAN, mpireal, mpitype, errcode)
CALL MPI_TYPE_COMMIT(mpitype, errcode)
cell_zface = mpitype
在这两种情况下,我都假设 starts(3)
等于 0
。