在上一篇中我们介绍了 mpi4py 中的简单并行 I/O 操作,下面我们将介绍 mpi4py 中的不连续读/写和集合 I/O 操作。
在前面的介绍中,我们没有设定文件视图,或者说使用了默认的文件视图。MPI 中的文件视图定义文件中对一个进程可见的部分,一个 MPI 读/写操作方法只能操作文件中其可见的部分,而会跳过其不可见的部分。当一个文件刚被打开时,默认情况下,整个文件的内容对打开该文件的进程组中的所有进程都是可见的,MPI 把这个文件当成由连续的二进制字节构成(而不是任何有类型的数据)。文件刚打开时,每个进程的独立文件指针和共享文件指针都被设置成了文件起始位置(即偏移为 0 的位置)。
采用默认文件视图,并且使用上一篇中介绍的 Read/Write 或 Read_at/Write_at 文件读/写方法,每个进程每次只能读/写文件中的一段连续数据。其实这种文件操作功能用普通的 Unix/Linux I/O 读/写函数也能实现,因此并不能体现出 MPI 并行 I/O 操作的优势。在实际的并行科学计算应用中,每个进程通常需要读/写很多处于文件中不同位置的很多小的连续数据块。当然可以使用前面介绍的读/写方法每次读/写一个这种小的数据块,但是由于 I/O 操作本身高的时延,这种操作是非常低效和费时的。MPI 提供了更高效的操作方法,允许使用一次函数调用读/写不连续的数据块。如果将这种操作方式和集合 I/O 结合起来,则能进一步提高 I/O 操作性能。因为这种组合可以给 MPI 实现提供足够的信息使其知道有哪些进程在同时读/写文件及文件中所有会被同时读/写到的数据。MPI 实现可以利用这些信息对该 I/O 操作实施一些优化,比如说将各个进程的小的数据块合并成若干大的连续数据块一次性进行操作等,以显著地提高程序的 I/O 性能。
MPI 的不连续读/写功能是使用文件视图结合相应的读/写函数实现的,下面我们首先介绍文件视图相关方法。
文件视图和不连续读/写
MPI.File.Set_view(self, Offset disp=0, Datatype etype=None, Datatype filetype=None, datarep=None, Info info=INFO_NULL)
MPI.File.Get_view(self)
上面两个方法在前面已经作过介绍,这里不再赘述。这里只强调几点:设置文件视图的操作是一个集合操作,必须打开文件的进程组中的所有进程一起参与。当使用独立文件指针或显式偏移地址方法时,每个进程可以根据需要设置一个不同的文件视图;但是当使用共享文件指针时,所有的进程必须设置一个同样的视图。在程序中文件视图可以多次重新设置。当一个文件被打开后,如果不设置其文件视图,则会使用默认的文件视图:即初始偏移 disp
为 0,etype
和 filetype
都为 MPI.BYTE。
下面这张图给出了文件视图的一个例子:disp
为 5 × 4 字节,etype
为 MPI.INT,filetype
为 2 个整数加 4 个整数大小的一段空隙。如果某个进程设置了以上文件视图,则该进程只能读/写到文件中如上图阴影部分的数据,其它位置的数据会被跳过。在下面的例程中,我们将展示如何设置这样的文件视图,及如果利用此文件视图结合上一篇中介绍的文件读/写方法一次性向文件写入或者从文件读取大量不连续的数据。
下面是上一篇中介绍的文件读/写方法的集合操作版本。
集合操作
独立文件指针
MPI.File.Read_all(self, buf, Status status=None)
MPI.File.Read 的集合操作版本,参数也相同,不同之处在于该方法必须被打开该文件的进程组中的进程一起调用,而 MPI.File.Read 则可以被某个或某几个进程调用而无需其它进程一起参与。该方法也是使用独立文件指针的阻塞调用。
MPI.File.Write_all(self, buf, Status status=None)
MPI.File.Write 的集合操作版本,参数也相同,不同之处在于该方法必须被打开该文件的进程组中的进程一起调用,而 MPI.File.Write 则可以被某个或某几个进程调用而无需其它进程一起参与。该方法也是使用独立文件指针的阻塞调用。
显式偏移地址
MPI.File.Read_at_all(self, Offset offset, buf, Status status=None)
MPI.File.Read_at 的集合操作版本,参数也相同,不同之处在于该方法必须被打开该文件的进程组中的进程一起调用,而 MPI.File.Read_at 则可以被某个或某几个进程调用而无需其它进程一起参与。该方法也是使用显式偏移地址的阻塞调用。
MPI.File.Write_at_all(self, Offset offset, buf, Status status=None)
MPI.File.Write_at 的集合操作版本,参数也相同,不同之处在于该方法必须被打开该文件的进程组中的进程一起调用,而 MPI.File.Write_at 则可以被某个或某几个进程调用而无需其它进程一起参与。该方法也是使用显式偏移地址的阻塞调用。
例程
下面给出使用例程。
# noncontig_io.py
"""
Demonstrates the usage of noncontiguous accesses and collective I/O.
Run this with 3 processes like:
$ mpiexec -n 3 python noncontig_io.py
"""
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
buf1 = np.arange(10, dtype='i')
buf2 = np.zeros(10, dtype='i') # initialize to all zeros
filename = 'temp.txt'
# open the file for read and write, create it if it does not exist,
# and delete it on close
fh = MPI.File.Open(comm, filename, amode= MPI.MODE_CREATE | MPI.MODE_RDWR | MPI.MODE_DELETE_ON_CLOSE)
# displacement in bytes of each process
disp = (5 + rank * 2) * MPI.INT.Get_size()
# the etype
etype = MPI.INT
# construct filetype, which consists of 2 ints and a gap of 4 ints
INT2 = MPI.INT.Create_contiguous(2)
filetype = INT2.Create_resized(0, 6*MPI.INT.Get_size())
filetype.Commit()
# set the file view, which is a collective operation
fh.Set_view(disp, etype, filetype)
if rank == 0:
# rank 0 writes buf1 to the file
# the 10 interges will be writen into noncontiguous positions
# in the file with a single write call
fh.Write(buf1)
# rank 0 reads data from file to buf2
# 10 interges in noncontiguous positions of the file will be read
# with a single read call
fh.Read_at(0, buf2)
print 'buf2:', buf2
# use collective I/O method
# first reset the individual file pointer to the beginning of the file
fh.Seek(0, whence=MPI.SEEK_SET)
# collectively write the data to file
fh.Write_all(buf1)
# reset the file view for read, which is a collective operation
fh.Set_view(disp, etype, etype)
# check what's in the file
if rank == 0:
buf3 = np.zeros(30, dtype='i') # initialize to all zeros
fh.Seek(0, whence=MPI.SEEK_SET)
fh.Read(buf3)
print 'buf3:', buf3
# buf3: [0 1 0 1 0 1 2 3 2 3 2 3 4 5 4 5 4 5 6 7 6 7 6 7 8 9 8 9 8 9]
# rank 0 --- --- --- --- ---
# rank 1 --- --- --- --- ---
# rank 2 --- --- --- --- ---
# close the file
fh.Close()
运行结果如下:
$ mpiexec -n 3 python noncontig_io.py
buf2: [0 1 2 3 4 5 6 7 8 9]
buf3: [0 1 0 1 0 1 2 3 2 3 2 3 4 5 4 5 4 5 6 7 6 7 6 7 8 9 8 9 8 9]
以上介绍了 mpi4py 中的不连续读/写和集合 I/O 操作,在下一篇中我们将介绍 mpi4py 中读/写文件中数组的方法。