在上一篇中我们介绍了 mpi4py 中的并行文件视图操作方法,下面我们将介绍访问文件数据的相关方法。
文件访问方法
MPI 环境下应用程序对文件的访问主要有 3 种特征:定位(positioning),包括显式偏移和隐式文件指针;同步性(synchronism),主要包括阻塞、非阻塞和分步集合操作;集合性(coordination),主要包括非集合操作和集合操作。下表列出的相应的访问方法(MPI.File 类方法)反应了这些操作特征的组合,其中结合了两种类型的文件指针——独立文件指针和共享文件指针。
上表中访问文件数据的方法很多,不能在此一一详细介绍,但是它们根据访问文件的特征却有其规律性,下面我们介绍 3 种访问特征的具体含义和数据访问的基本规范:
定位(positioning)
MPI 中的文件定位有 3 种:显式偏移(explicit offset)、独立文件指针(individual file pointers)和共享文件指针(shared file pointers),相同进程中可混合使用这 3 种操作方式,彼此无影响。
所有使用显式指针的方法,其命名中都包含 “_at“ 字符,使用显式指针访问数据时,其参数为直接指定的文件偏移位置,而不必再通过任何文件指针,也与文件视图中的相对偏移位置无关。注意:这里使用偏移不需要执行 seek 操作。
所有使用独立文件指针的数据访问方法,其命名中不包含与定位有关的字符串。
所有使用共享文件指针访问数据的方法,其命名中均包含 “_shard“ 或 “_ordered“ 字符串。
对使用指针访问文件的方法而言,MPI 负责维护其文件指针,使用时要注意的关键问题是界定相应 I/O 动作何时以及如何更新文件指针。通常情况下,I/O 操作结束时会控制文件指针指向其最后一个访问过的数据项之后第一个数据项的起始位置,而非阻塞或分步集合操作的启动方法就能修改文件指针,因此有可能在实际数据访问结束之前,文件指针就已经被修改指向新的位置。
同步性(synchronism)
MPI 支持 3 种同步类型:阻塞、非阻塞以及专门用于集合数据访问函数的带约束非阻塞操作——分步集合操作。
与非阻塞点到点通信类似,非阻塞的 I/O 操作会发起一个 I/O 操作让 MPI 在后台实现数据访问,把其它计算与数据访问重叠起来,然后再在必要位置设置测试结束函数(如 Wait, Test 等)确认数据访问操作是否真正完成。所有非阻塞 I/O 操作方法都以 I 开头命名。在发起非阻塞操作到测试结束函数返回正确执行结果之前,数据访问操作所使用的缓冲区不应再作为其它通信操作的缓冲区复用。
集合性(coordination)
所有非集合操作方法都有对应的集合操作版本,其对应关系为: xxx 对应于 xxx_all 或一对 xxx_begin/xxx_end;xxx_shared 对应的集合操作方法为 xxx_ordered。一般来说,全局数据访问具有更大的优化潜力,因此集合操作通常比相应的非集合操作方法性能更好。
数据访问基本规范(data access conventions)
读序列方法从文件读数据到进程空间,写序列方法负责把进程内存空间的数据写入文件,所有文件操作都通过文件句柄进行,通过相对于当前视图的偏移(offset)定位数据在文件中的位置,内存中的数据由三元组(buf, count, datatype)描述,操作结束时通过状态变量返回操作所涉及的数据数量。
偏移量(offset)指定访问文件的起始位置,以 etype 为单位,取相对于当前视图的计数。如果方法使用显式偏移量(explicit offset),则在其参数中指定偏移参数。操作文件指针的方法则使用隐式偏移量(implicit offset)。
通常访问文件数据的方法都在用户缓冲区 buf 和文件 file 之间传递类型为 datatype 的数据项 count 个。要求 datatype 的类型定义必是当前视图 etype 类型的若干个连续复制。与点到点通信的接收操作类似,不能使用内部定义了重叠区域的类型进行文件读写操作。
对非阻塞操作,与点到点通信类似,MPI 启动异步操作的同时注册一个 MPI.Request 对象,然后通过其 Test,Wait 等方法测试 I/O 操作是否完成。
对阻塞操作,操作完成即可得到纪录操作过程的状态对象;而对非阻塞操作和分步集合操作,经测试操作完成后才能返回正确的状态对象。一次操作所涉及的 datatype 数据个数以及其它一些通信信息都可通过 MPI.Status.Get_count,MPI.Status.Get_elements 等状态对象的方法提取出来。另外也可通过状态对象的 MPI.Status.Is_cancelled 方法获得操作是否被取消。
对读取操作,测试文件末尾的方法是判断读到的数据项个数少于参数预先指定的个数。对写操作,超过文件末尾会自动导致扩大文件大小。
文件指针及其定位操作
以上介绍的部分文件数据访问方法需要辅助以文件指针及其定位操作才能完成,下面给出相关方法(MPI.File 类方法)接口:
独立文件指针
Seek(self, Offset offset, int whence=SEEK_SET)
根据 whence
参数指定的方式更新进程各自的文件指针到指定偏移位置 offset
。whence
可能的取值如下:
- MPI.SEEK_SET:将文件指针设置为
offset
参数给出的值; - MPI.SEEK_CUR:将文件指针设置为相对于当前位置的
offset
偏移处,即当前位置加上offset
参数; - MPI.SEEK_END:将文件指针设置为指向文件末尾再加上
offset
参数的值。
offset
的值可为负,但要注意在文件视图中指定相对于视图起始位置负数的偏移会导致错误。
Get_position(self)
返回独立文件指针在当前文件视图中的相对偏移位置,以当前视图的 etype
为计量单位。
Get_byte_offset(self, Offset offset)
将相对视图的偏移量 offset
转换为以字节为单位的绝对偏移位置(相对于文件物理起始位置的字节偏移量)并返回。
共享文件指针
Seek_shared(self, Offset offset, int whence=SEEK_SET)
执行集合操作,根据 whence
参数移动共享文件指针到指定偏移位置 offset
。whence
可能的取值同 Seek 方法。
Get_position_shared(self)
返回共享文件指针相对当前文件视图的位置,以 etype 为单位。
文件一致性
文件一致性是指解决多个操作访问同一个文件时的一致性。MPI 程序访问文件都通过一个集合操作 Open 打开的文件句柄来执行,应提供以下 3 个级别的一致性。
- 使用相同文件句柄访问文件的串行一致性;
- 使用同一个集合操作(Open)以原子模式(atomic mode)打开的若干文件句柄访问文件的串行一致性;
- 其它应用程序根据需要自定义的访问一致性。
所谓串行一致性是指要求一组访问文件的操作按照一定顺序依次实施,每个访问都应是原子操作,但允许具体执行时的顺序有所不同。应用程序自定义的一致性可通过程序语句顺序加之以 Sync 实现。
文件一致性的相关方法(MPI.File 类方法)接口如下:
Set_atomicity(self, bool flag)
设置当前打开文件的原子模式,如果 flag
为 True,则设置为原子模式,否则设置为非原子模式。该方法执行一个集合操作,参与该操作的进程组必须传递相同的 flag
参数值。
Get_atomicity(self)
返回当前一致性约束的状态。如果是原子模式则返回 True,否则返回 False。
Sync(self)
该方法是一个集合操作,如果某个进程调用该方法,则到此为止该进程已经写入文件的数据全部刷新到磁盘上。如果其它进程也更新过文件,则此方法之后所有更新的结果都会对组内进程可见。在调用此方法之前,应用程序应确保文件句柄上的所有非阻塞操作、分步集合操作以及其它操作都已经完成,否则可能导致一些随机错误。
例程
下面给出部分文件访问操作方法的使用例程。
# file_io.py
"""
Demonstrates the usage of Open, Set_atomicity, Set_view, Seek,
Read, Write, Get_position, Close.
Run this with 4 processes like:
$ mpiexec -n 4 python file_io.py
"""
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
filename = 'temp.txt'
# open the file, create it if it does not exist
fh = MPI.File.Open(comm, filename, amode= MPI.MODE_CREATE | MPI.MODE_RDWR | MPI.MODE_DELETE_ON_CLOSE)
# set to be atomicity mode to avoid read/write conflict
fh.Set_atomicity(True)
# set file view, use MPI.INT as etype and filetype
fh.Set_view(0, MPI.INT, MPI.INT)
num_ints = 10 # number of int types
buf1 = np.arange(num_ints, dtype='i')
buf2 = np.zeros(num_ints, dtype='i') # initialize to all zeros
# set individual file pointer of each process
fh.Seek(rank*num_ints, whence=MPI.SEEK_SET)
# each process writes buf1 to file
fh.Write(buf1)
# reset individual file pointer of each process
fh.Seek(rank*num_ints, whence=MPI.SEEK_SET)
# each process reads data to buf2 from file
fh.Read(buf2)
print 'process %d has buf2 = %s' % (rank, buf2)
# check position of individual file pointer
print 'process %d has file pointer position %d after read' % (rank, fh.Get_position())
# close the file
fh.Close()
运行结果如下:
$ mpiexec -n 4 python file_io.py
process 0 has buf2 = [0 1 2 3 4 5 6 7 8 9]
process 0 has file pointer position 10 after read
process 1 has buf2 = [0 1 2 3 4 5 6 7 8 9]
process 1 has file pointer position 20 after read
process 2 has buf2 = [0 1 2 3 4 5 6 7 8 9]
process 2 has file pointer position 30 after read
process 3 has buf2 = [0 1 2 3 4 5 6 7 8 9]
process 3 has file pointer position 40 after read
以上我们介绍了 mpi4py 中的访问文件数据操作方法,在下一篇中我们将介绍 mpi4py 的一些使用技巧。