在上一篇中我們介紹了 mpi4py 中的簡單并行 I/O 操作,下面我們將介紹 mpi4py 中的不連續(xù)讀/寫和集合 I/O 操作。
在前面的介紹中,我們沒有設(shè)定文件視圖,或者說使用了默認(rèn)的文件視圖。MPI 中的文件視圖定義文件中對(duì)一個(gè)進(jìn)程可見的部分,一個(gè) MPI 讀/寫操作方法只能操作文件中其可見的部分,而會(huì)跳過其不可見的部分。當(dāng)一個(gè)文件剛被打開時(shí),默認(rèn)情況下,整個(gè)文件的內(nèi)容對(duì)打開該文件的進(jìn)程組中的所有進(jìn)程都是可見的,MPI 把這個(gè)文件當(dāng)成由連續(xù)的二進(jìn)制字節(jié)構(gòu)成(而不是任何有類型的數(shù)據(jù))。文件剛打開時(shí),每個(gè)進(jìn)程的獨(dú)立文件指針和共享文件指針都被設(shè)置成了文件起始位置(即偏移為 0 的位置)。
采用默認(rèn)文件視圖,并且使用上一篇中介紹的 Read/Write 或 Read_at/Write_at 文件讀/寫方法,每個(gè)進(jìn)程每次只能讀/寫文件中的一段連續(xù)數(shù)據(jù)。其實(shí)這種文件操作功能用普通的 Unix/Linux I/O 讀/寫函數(shù)也能實(shí)現(xiàn),因此并不能體現(xiàn)出 MPI 并行 I/O 操作的優(yōu)勢。在實(shí)際的并行科學(xué)計(jì)算應(yīng)用中,每個(gè)進(jìn)程通常需要讀/寫很多處于文件中不同位置的很多小的連續(xù)數(shù)據(jù)塊。當(dāng)然可以使用前面介紹的讀/寫方法每次讀/寫一個(gè)這種小的數(shù)據(jù)塊,但是由于 I/O 操作本身高的時(shí)延,這種操作是非常低效和費(fèi)時(shí)的。MPI 提供了更高效的操作方法,允許使用一次函數(shù)調(diào)用讀/寫不連續(xù)的數(shù)據(jù)塊。如果將這種操作方式和集合 I/O 結(jié)合起來,則能進(jìn)一步提高 I/O 操作性能。因?yàn)檫@種組合可以給 MPI 實(shí)現(xiàn)提供足夠的信息使其知道有哪些進(jìn)程在同時(shí)讀/寫文件及文件中所有會(huì)被同時(shí)讀/寫到的數(shù)據(jù)。MPI 實(shí)現(xiàn)可以利用這些信息對(duì)該 I/O 操作實(shí)施一些優(yōu)化,比如說將各個(gè)進(jìn)程的小的數(shù)據(jù)塊合并成若干大的連續(xù)數(shù)據(jù)塊一次性進(jìn)行操作等,以顯著地提高程序的 I/O 性能。
MPI 的不連續(xù)讀/寫功能是使用文件視圖結(jié)合相應(yīng)的讀/寫函數(shù)實(shí)現(xiàn)的,下面我們首先介紹文件視圖相關(guān)方法。
文件視圖和不連續(xù)讀/寫
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)
上面兩個(gè)方法在前面已經(jīng)作過介紹,這里不再贅述。這里只強(qiáng)調(diào)幾點(diǎn):設(shè)置文件視圖的操作是一個(gè)集合操作,必須打開文件的進(jìn)程組中的所有進(jìn)程一起參與。當(dāng)使用獨(dú)立文件指針或顯式偏移地址方法時(shí),每個(gè)進(jìn)程可以根據(jù)需要設(shè)置一個(gè)不同的文件視圖;但是當(dāng)使用共享文件指針時(shí),所有的進(jìn)程必須設(shè)置一個(gè)同樣的視圖。在程序中文件視圖可以多次重新設(shè)置。當(dāng)一個(gè)文件被打開后,如果不設(shè)置其文件視圖,則會(huì)使用默認(rèn)的文件視圖:即初始偏移 disp 為 0,etype 和 filetype 都為 MPI.BYTE。
下面這張圖給出了文件視圖的一個(gè)例子:disp 為 5 × 4 字節(jié),etype 為 MPI.INT,filetype 為 2 個(gè)整數(shù)加 4 個(gè)整數(shù)大小的一段空隙。如果某個(gè)進(jìn)程設(shè)置了以上文件視圖,則該進(jìn)程只能讀/寫到文件中如上圖陰影部分的數(shù)據(jù),其它位置的數(shù)據(jù)會(huì)被跳過。在下面的例程中,我們將展示如何設(shè)置這樣的文件視圖,及如果利用此文件視圖結(jié)合上一篇中介紹的文件讀/寫方法一次性向文件寫入或者從文件讀取大量不連續(xù)的數(shù)據(jù)。

下面是上一篇中介紹的文件讀/寫方法的集合操作版本。
集合操作
獨(dú)立文件指針
MPI.File.Read_all(self, buf, Status status=None)
MPI.File.Read 的集合操作版本,參數(shù)也相同,不同之處在于該方法必須被打開該文件的進(jìn)程組中的進(jìn)程一起調(diào)用,而 MPI.File.Read 則可以被某個(gè)或某幾個(gè)進(jìn)程調(diào)用而無需其它進(jìn)程一起參與。該方法也是使用獨(dú)立文件指針的阻塞調(diào)用。
MPI.File.Write_all(self, buf, Status status=None)
MPI.File.Write 的集合操作版本,參數(shù)也相同,不同之處在于該方法必須被打開該文件的進(jìn)程組中的進(jìn)程一起調(diào)用,而 MPI.File.Write 則可以被某個(gè)或某幾個(gè)進(jìn)程調(diào)用而無需其它進(jìn)程一起參與。該方法也是使用獨(dú)立文件指針的阻塞調(diào)用。
顯式偏移地址
MPI.File.Read_at_all(self, Offset offset, buf, Status status=None)
MPI.File.Read_at 的集合操作版本,參數(shù)也相同,不同之處在于該方法必須被打開該文件的進(jìn)程組中的進(jìn)程一起調(diào)用,而 MPI.File.Read_at 則可以被某個(gè)或某幾個(gè)進(jìn)程調(diào)用而無需其它進(jìn)程一起參與。該方法也是使用顯式偏移地址的阻塞調(diào)用。
MPI.File.Write_at_all(self, Offset offset, buf, Status status=None)
MPI.File.Write_at 的集合操作版本,參數(shù)也相同,不同之處在于該方法必須被打開該文件的進(jìn)程組中的進(jìn)程一起調(diào)用,而 MPI.File.Write_at 則可以被某個(gè)或某幾個(gè)進(jìn)程調(diào)用而無需其它進(jìn)程一起參與。該方法也是使用顯式偏移地址的阻塞調(diào)用。
例程
下面給出使用例程。
# 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()
運(yùn)行結(jié)果如下:
$ 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 中的不連續(xù)讀/寫和集合 I/O 操作,在下一篇中我們將介紹 mpi4py 中讀/寫文件中數(shù)組的方法。