在上一篇中我们介绍了非线程安全的 Probe 和 MPI-3 中线程安全的 Mprobe,下面将介绍 MPI-3 中大的计数及相关函数。
在 MPI-1 和 MPI-2 标准中,所有的通信和 I/O 函数/方法中的计数参数都使用的是 C 语言 int 或者 Fortran INTEGER 类型,在大多数系统中,它们都是用 32 位来表示的。一个 32 位的 C int 数所能表示的最大整数是 2147483647,超过该值就会发生溢出,此即一些通信和 I/O 函数中能够发送/接收的最大数据类型个数,如果使用 MPI 中预定义的数据类型,如 MPI.INT 或 MPI.FLOAT,则单次能够发送/接收的数据量的上限大概是 2 GB。这对一些数据量比较大的应用是一个比较严重的限制。
MPI 论坛也意识到这个问题,一种可行的解决方案是为这些函数都定义一个新的版本,在这些新版本中使用 64 位或更大的整数类型,不过这样会使得 MPI 标准中的函数数量大为增加。MPI 论坛采用了另外一种解决方案,那就是利用 MPI 对派生数据类型的支持,再额外定义一个 MPI_Count 数据类型及若干使用该数据类型的查询函数。通过此种方法,如果要发送/接收计数超过 32 位最大整数的某种类型数据,则可以首先由该数据类型作为基本类型创建派生数据类型,只要所发送/接收的数据按照派生数据类型计数不超过 32 位最大整数,通信操作就能成功执行,并且可以用这些新定义的查询函数来得到大的消息及数据类型的相关信息,而不至于发生溢出。
MPI-3 新定义的 MPI_COUNT 数据类型被定义为足够大的整数以使其足以表示 MPI 支持的内存地址和文件偏移。新定义的查询函数与已经存在的对应函数完成相同的功能,不过在名称后面加了 _x 后缀,并且 size,lb,extent 等参数都使用了 MPI_Count 数据类型,其 C 语言接口如下:
MPI_Type_size_x(MPI_Datatype datatype, MPI_Count *size)
MPI_Type_get_extent_x(MPI_Datatype datatype, MPI_Count *lb, MPI_Count *extent)
MPI_Type_get_true_extent_x(MPI_Datatype datatype, MPI_Count *lb, MPI_Count *extent)
另外,新增 status 状态中设置和获取大的计数, 其 C 语言接口如下:
MPI_Get_elements_x(const MPI_Status *status, MPI_Datatype datatype, MPI_Count *count)
MPI_Status_set_elements_x(MPI_Status *status, MPI_Datatype datatype, MPI_Count count)
mpi4py 3.0.0 支持 MPI-3 标准,因此以上介绍的新特性也可用,不过因为 Python 并不用声明数据类型,所以我们不用特别地关心 MPI.COUNT 数据类型,另外由于 Python 中的整型数默认就是 64 位的,因此也与大的计数兼容。虽然已经存在的无 _x 后缀版本的函数在 MPI-3 中依然可用,但是却并不推荐使用,应该尽量使用 MPI-3 所新引进的支持大的计数的相应函数。基于这一思想,且为了简洁,mpi4py 3.0.0 直接包装和调用了新的带有 _x 后缀的相关函数。因此在支持 MPI-3 的环境下,以下方法及属性都支持大的计数;但是在不支持 MPI-3 的环境下,它们会调用无 _x 后缀的函数版本。
方法接口
MPI.Datatype.Get_size(self)
MPI.Datatype.Get_extent(self)
MPI.Datatype.Get_true_extent(self)
MPI.Status.Get_elements(self, Datatype datatype)
MPI.Status.Set_elements(self, Datatype datatype, Count count)
属性
MPI.Datatype.size
MPI.Datatype.extent
MPI.Datatype.lb
MPI.Datatype.ub
MPI.Datatype.true_extent
MPI.Datatype.true_lb
MPI.Datatype.true_ub
以上方法和属性获取数据类型的类型图的相关信息,类型图及其相关信息在前面已经介绍过,此处不再赘述。
限制
以上方案虽然能够解决很大一部分涉及大的消息和大的计数的问题,但也有若干限制。
集合规约操作
以上解决方案的一个限制是执行大量数据的规约操作,如 MPI.Comm.Reduce 和 MPI.Comm.Allreduce。MPI 预定义的规约算符 只能使用在预定义数据类型上,不能使用在派生数据类型上。因此对大量数据的规约操作,如果使用派生数据类型,则必须自己定义对派生数据类型的规约操作算符(可以通过 MPI.Op.Create 来定义)。
不规则集合操作
另一个限制是在对大量数据的不规则集合操作上,如 MPI.Comm.Gatherv,MPI.Comm.Alltoallv,MPI.Comm.Alltoallw。对 MPI.Comm.Gatherv 和 MPI.Comm.Alltoallv,每个进程可以发送不同数量但是相同类型的数据。在一些情况下,可能没法定义一个能满足所有进程的派生数据类型,比如说各个进程所发送的数据量不存在一个公约数时。在此种情况下,对大量数据的 MPI.Comm.Gatherv,MPI.Comm.Alltoallv 操作将无法进行。MPI.Comm.Alltoallw 虽然可以使用多个数据类型,但是它的以字节为单位的偏移参数却用的是 C 语言 int 类型,这就限制了最大可用的偏移范围。
对这些不规则的集合操作,如果要对大量的数据操作,一种方法是采用一系列的点到点操作来替换,虽然这样可能达不到最好的运行性能。另一种方法是使用 MPI-3 新引进的近邻集合操作,如 MPI.Topocomm.Neighbor_alltoallw,该方法不同于 MPI.Comm.Alltoallw 的地方在于其偏移参数使用的是 MPI_Aint 而不是 C 语言 int,MPI_Aint 可以表示的数值范围比 C 语言 int 类型更大。
例程
下面给出使用例程。
# large_count.py
"""
Demonstrates the large counts in MPI-3.
Run this with 2 processes like:
$ mpiexec -n 2 python large_count.py
"""
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.rank
MAX_INT32 = np.iinfo(np.int32).max
print 'Max value of a 32 bits int: %d' % MAX_INT32
# create a derived datatype conposed of MAX_INT32 MPI.INT
LARGE_TYPE = MPI.INT.Create_contiguous(MAX_INT32)
print 'LARGE_TYPE:', LARGE_TYPE.lb, LARGE_TYPE.ub, LARGE_TYPE.size, LARGE_TYPE.extent
# create a derived datatype conposed of 2*MAX_INT32 MPI.INT
LARGE_TYPE2 = LARGE_TYPE.Create_contiguous(2)
print 'LARGE_TYPE2:', LARGE_TYPE2.lb, LARGE_TYPE2.ub, LARGE_TYPE2.size, LARGE_TYPE2.extent
# commit the derived datatypes
LARGE_TYPE.Commit()
LARGE_TYPE2.Commit()
if rank == 0:
large_array = np.ones(2*MAX_INT32, dtype=np.int32)
print 'rank 0 sends a large message of', 1.0 * large_array.nbytes / 2**32, 'GB'
# comm.Send([large_array, 1, LARGE_TYPE2], dest=1, tag=11)
# or
comm.Send([large_array, 2, LARGE_TYPE], dest=1, tag=11)
else:
recv_buf = np.empty(2*MAX_INT32, dtype=np.int32)
comm.Recv([recv_buf, 1, LARGE_TYPE2], source=0, tag=11)
# or
# comm.Recv([recv_buf, 2, LARGE_TYPE], source=0, tag=11)
print 'rank 1 successfully received the large message.'
运行结果如下:
$ mpiexec -n 2 python large_count.py
Max value of a 32 bits int: 2147483647
LARGE_TYPE: 0 8589934588 8589934588 8589934588
LARGE_TYPE2: 0 17179869176 17179869176 17179869176
Max value of a 32 bits int: 2147483647
LARGE_TYPE: 0 8589934588 8589934588 8589934588
LARGE_TYPE2: 0 17179869176 17179869176 17179869176
rank 0 sends a large message of 3.99999999814 GB
rank 1 successfully received the large message.
以上介绍了 MPI-3 中大的计数及相关函数,在下一篇中我们将介绍 MPI 3.1 新增的若干功能。