1.8 并行I/O
许多并行科学应用需要从文件中读写大量数据,例如读取输入网格、程序暂停保存数据/重启暂停程序、数据分析和虚拟化。如果文件I/O效率不高,将成为程序性能的瓶颈。MPI为程序应用开发者提供简明的并行I/O函数接口,能够显著地提高文件I/O访问性能。
MPI提供的I/O接口保留在POSIX文件中常用的I/O操作,例如打开、关闭、查找、读取和写入操作。同时,MPI提供许多高级的特性,例如不连续访问内存和文件中数据、聚合I/O和传递有关性能的提示给MPI实现。
图1-10是每个进程从一个共享文件的不同位置并行读取数据的简单程序示例。MPI提供多种读取数据的实现方式。图1-11是一种最简单的使用独立文件I/O和文件指针读取数据的程序示例。每个进程通过MPI_File_open函数打开文件,其中,该函数的第一个参数MPI_COMM_WORLD代表通信域,第二个参数代表需要打开的文件路径和文件名,第三个参数代表文件打开方式,第四个参数通过在MPI_Info对象中附加一对键值向I/O实现中传递信息,第五个参数是I/O操作返回的句柄,可用于后续I/O操作。在第四个参数MPI_Info中,可传递文件分片和内部缓冲区大小等信息,用于优化MPI I/O操作。
图1-10 每个进程从一个共享文件中读取一块数据
每个进程通过调用MPI_File_seek函数,根据读取位移量,将文件指针移动到相应的位置。在该程序示例中,每个进程的文件指针是相互独立的。MPI也提供共享文件指针的I/O操作,即多个进程共享一个文件指针。通过MPI_File_read函数,将mgsieze个整数类型数据从文件中读取到内存缓冲区中。最后,通过调用MPI_File_close函数,实现关闭文件和结束I/O操作。图1-11的所示的MPI I/O读取数据的方式与POSIX I/O方式类似。
MPI提供的第二种读取文件内容的方式是避免使用文件指针,直接根据偏移量读取数据。通过调用MPI_File_read_at函数实现显示偏移并行文件读取功能,只需在该函数参数中指定偏移量即可,不再需要调用MPI_File_seek函数。由于MPI_File_read_at函数不需要文件指针,因此该函数实现的I/O操作是线程安全的。
MPI_File_read和MPI_File_read_at是独立调用的I/O函数,而非聚合调用函数。每个进程均可独立调用这两个函数,无需所有进程都必须调用同样的函数。因此,MPI实现无法得知多少进程会调用这两个函数,也无法对这两个函数实现的I/O操作进行性能优化。
MPI也提供聚合I/O读写方式,并在聚合I/O读写方式的名字中添加“_all”关键字,例如MPI_File_read_all和MPI_File_read_at_all函数。聚合I/O读写方式的函数参数与相应的独立I/O读写方式类似,区别在于聚合I/O读写方式需要所有进程调用I/O读写函数。因此,在聚合I/O读写方式中,MPI可对性能进行优化,如文献[268,269]所示的优化方式。通常,建议程序开发者尽量采用聚合I/O读写方式,而非独立I/O读写方式。
图1-12程序示例是采用第三种方式实现图1-10中所示的文件数据读取,即“文件视口”方式。MPI_File_set_view函数用于指定文件I/O访问视口,即指定文件需要读写或者跳过访问的数据。文件视口通过“偏移、基本类型和文件类型”三元参数表达:偏移指定视口在文件中的起始位置,例如相对文件头的偏移位置;基本类型描述文件I/O访问的基本数据类型;文件类型由基本类型构成。文件视口通过每个进程上定义的三元参数组成。
在图1-12中,每个进程上指定的偏移量为rank×msgsize,基本类型为MPI_INT,文件类型也为MPI_INT。文件类型之后的参数用于指定数据表示方式,“native”表示文件数据存储方式与内存一致。MPI_File_set_view函数中最后一个参数用于传递运行时的信息。在文件读取时,可以采用独立或者聚合两种方式,图1-12中采用聚合读取文件数据方式。每个进程从文件视口中读取msgsize个整数到内存缓冲区中。因为文件视口中偏移量不同,所以每个进程从文件中不同位置读取文件数据。
MPI提供高效的I/O访问方式,尤其适合每个进程访问文件中不同位置的数据,例如访问子数组或者分布式数组中的数据。在非连续I/O访问方式中,MPI-I/O访问方式性能优越于POSIX I/O访问方式,在一些程序中,MPI-I/O访问性能是POSIX I/O的1000倍。关于MPI I/O的详细介绍可参考文献[127]。