mpi4py 中获得高性能 I/O 的方法和建议

上一篇中我们介绍了 mpi4py 中的文件互操作性,下面我们将介绍 mpi4py 中获得高性能 I/O 的方法和建议。

MPI 在底层实现中可充分利用集合操作和非连续数据读/写进行面向文件系统/设备的特殊优化。因此使用 MPI I/O 操作,最重要的就是要活用其提供的几个特征:单次非连续数据访问,集合操作,非阻塞操作,理解并灵活设置适合文件系统特征的各 hint,等。

由于底层可能提供的优化措施,应尽量使更多的进程参与 I/O 操作而不是将其集中在一个进程上。对非连续 I/O,应定义相应的派生数据类型描述“非连续性”,进而再利用集合操作让多个进程一起提交数据 I/O 请求。通常情况下应用程序都有自己的计算模式进而也可找出其 I/O 访问特征,要针对不同的 I/O 特征结合底层硬件设计相应的执行模型。对相同的 I/O 访问特征,也能以不同的发起方式提交给 I/O 系统,比如说使用哪一个具体的读/写方法及怎样调用该读/写方法等。

4 个级别的访问模式

一般可以将 I/O 访问模式分为 4 个级别。

我们以一个具体的例子来讲解这四个不同的访问级别。考虑一个以块状形式分布在16 个进程上的二维数组,具体的分布情况如下图所示:

数组分布及访问特征

这个数组作为一个整体以行优先的顺序存储在一个文件中,如上图所示,文件中首先是 0 号进程本地子数组的第一行,紧接着是 1 号进程本地子数组的第一行,然后是 2 号进程本地子数组的第一行,然后是 3 号进程本地子数组的第一行,接下来是 0 号进程本地子数组的第二行,1 号进程本地子数组的第二行,等等。现在每个进程要从这个文件中读取该进程本地的数据。可以看出,每个进程的本地数据都是以不连续的小块方式存储于文件的不同位置。

对这个例子,4 个级别的访问模式分别为:

级别 0:各个进程按照类似 Unix/Linux 中 I/O 操作方式独立地一次读取本地子数组的一行。具体来说,像下面这样:

fh = MPI.File.Open(..., filename, ...)
for i in range(num_local_rows):
    fh.Seek(...)
    fh.Read(row[i], ...)
fh.Close()

级别 1:基本同级别 0,不过采用集合 I/O 操作。具体来说,像下面这样:

fh = MPI.File.Open(MPI.COMM_WORLD, filename, ...)
for i in range(num_local_rows):
    fh.Seek(...)
    fh.Read_all(row[i], ...)
fh.Close()

级别 2:各个进程定义派生数据类型作为 filetype 描述数据分布方式,然后独立访问自己负责的数据块。具体来说,像下面这样:

subarray = MPI.INT.Create_subarray(...)
subarray.Commit()
fh = MPI.File.Open(..., filename, ...)
fh.Set_view(..., subarray, ...)
fh.Read(local_array, ...)
fh.Close()

级别 3:基本同级别 2,不过采用集合 I/O 操作。具体来说,像下面这样:

subarray = MPI.INT.Create_subarray(...)
subarray.Commit()
fh = MPI.File.Open(MPI.COMM_WORLD, filename, ...)
fh.Set_view(..., subarray, ...)
fh.Read_all(local_array, ...)
fh.Close()

这 4 个级别的访问模式代表着逐步更多数据量的单次 I/O 请求,就像下图所示:

4 个级别的访问模式

每次 I/O 请求的数据量越大,MPI 实现就有更大的机会来产生更高的性能。因此只要可能,用户应该尽量使用级别 3 的数据访问模式。

当应用程序的每个进程只需要访问文件中单独一整块连续的数据,级别 0 和级别 2 将变的等价,级别 1 和级别 3 也等价,此时用户没有必要定义派生数据类型,直接使用级别0 或级别 1 也能取得高的性能。

高性能 I/O 优化措施

MPI IO 库内优化措施

有了上述讨论的访问特征信息,MPI IO 的底层实现可实施如下优化:

  • 数据筛选。从物理设备读取大块数据,然后再从中提取进程自己所需部分。
  • 集合 I/O 操作。将来自若干进程的数据访问请求组合形成一个大的数据访问请求。
  • 预取和缓冲。根据访问策略将数据从磁盘预先调入缓冲区。

应用程序级优化措施

  • 分析应用程序数据输入输出的特点。如果可能尽量使用级别 3 的 I/O 操作方法。
  • 尽量向 MPI IO 库提供更多的精准信息。如打开文件时使用精准的打开模式,即不设置运行时不需要的多余访问模式;运行中可能的情况下尽量使用集合操作;尽量减少操作个数和次数以及减少数据移动等。
  • 文件组织结构要与进程拓扑相匹配。结合数据访问模式和使用特点,在数据分解时刻意组织数据在数组各维的分布以使得 I/O 时可操作的连续数据块尽量大。
  • 了解环境信息。收集 I/O 设备的物理配置,逻辑组织结构,操作系统,文件系统的特征等信息,了解 MPI IO 在这些环境中可用的 hints 有哪些,分别起什么作用等。通过 MPI.File.Open,MPI.File.Set_view 等向 MPI 实现传递合适的 hints。
  • 考虑使用非阻塞 I/O 操作。非阻塞 I/O 操作虽然不能提高 I/O 本身的性能,但可能将 I/O 操作与部分计算和通信重叠起来提高程序的整体性能。

例程

下面给出使用例程。

# high_perf_io.py

"""
Demonstrates the four levels of access.

Run this with 16 processes like:
$ mpiexec -n 16 python high_perf_io.py
"""

import numpy as np
from mpi4py import MPI


comm = MPI.COMM_WORLD
rank = comm.Get_rank()

# create a p x q Cartesian process grid
p, q = 4, 4
cart_comm = comm.Create_cart([p, q])
# get the row and column coordinate of each process in the process grid
ri, ci = cart_comm.Get_coords(rank)

# the global array
lm, ln = 3, 2 # shape of local subarray
m, n = p*lm, q*ln # shape of global array
global_ary = np.arange(m*n, dtype='i').reshape(m, n)
rs, re = lm*ri, lm*(ri+1) # start and end of row
cs, ce = ln*ci, ln*(ci+1) # start and end of column
# local array of each process
local_ary = np.ascontiguousarray(global_ary[rs:re, cs:ce])
print 'rank %d has local_ary with shape %s' % (rank, local_ary.shape)

filename = 'temp.txt'

# the etype
etype = MPI.INT

# construct filetype
gsizes = [m, n] # global shape of the array
subsize = [lm, ln] # shape of local subarray
starts = [rs, cs] # global indices of the first element of the local array
filetype = MPI.INT.Create_subarray(gsizes, subsize, starts)
filetype.Commit()

# first write the global array to a file
if rank == 0:
    fh = MPI.File.Open(MPI.COMM_SELF, filename, amode=MPI.MODE_CREATE | MPI.MODE_WRONLY)
    fh.Write(global_ary)
    fh.Close()

comm.Barrier()


# level 0
# ----------------------------------------------------------------------------------

local_ary1 = np.zeros_like(local_ary)

fh = MPI.File.Open(comm, filename, amode=MPI.MODE_RDONLY)
for i in range(lm):
    offset = etype.Get_size() * ((rs + i) * n + cs)
    fh.Seek(offset)
    # each process uses individual read
    fh.Read(local_ary1[i, :])
    # fh.Read_at(offset, local_ary1[i, :])
assert np.allclose(local_ary, local_ary1)
fh.Close()


# level 1
# ----------------------------------------------------------------------------------

local_ary1 = np.zeros_like(local_ary)

fh = MPI.File.Open(comm, filename, amode=MPI.MODE_RDONLY)
for i in range(lm):
    offset = etype.Get_size() * ((rs + i) * n + cs)
    fh.Seek(offset)
    # use collective read
    fh.Read_all(local_ary1[i, :])
    # fh.Read_at_all(offset, local_ary1[i, :])
assert np.allclose(local_ary, local_ary1)
fh.Close()


# level 2
# ----------------------------------------------------------------------------------

local_ary1 = np.zeros_like(local_ary)

fh = MPI.File.Open(comm, filename, amode=MPI.MODE_RDONLY)
fh.Set_view(0, etype, filetype)
# each process uses individual read
fh.Read(local_ary1)
# fh.Read_at(0, local_ary1)
assert np.allclose(local_ary, local_ary1)
fh.Close()


# level 3
# ----------------------------------------------------------------------------------

local_ary1 = np.zeros_like(local_ary)

fh = MPI.File.Open(comm, filename, amode=MPI.MODE_RDONLY)
fh.Set_view(0, etype, filetype)
# use collective read
fh.Read_all(local_ary1)
# fh.Read_all_all(0, local_ary1)
assert np.allclose(local_ary, local_ary1)
fh.Close()

# remove the file
if rank == 0:
    MPI.File.Delete(filename)

运行结果如下:

$ mpiexec -n 16 python high_perf_io.py
rank 7 has local_ary with shape (3, 2)
rank 13 has local_ary with shape (3, 2)
rank 15 has local_ary with shape (3, 2)
rank 5 has local_ary with shape (3, 2)
rank 9 has local_ary with shape (3, 2)
rank 10 has local_ary with shape (3, 2)
rank 11 has local_ary with shape (3, 2)
rank 12 has local_ary with shape (3, 2)
rank 14 has local_ary with shape (3, 2)
rank 0 has local_ary with shape (3, 2)
rank 1 has local_ary with shape (3, 2)
rank 2 has local_ary with shape (3, 2)
rank 3 has local_ary with shape (3, 2)
rank 4 has local_ary with shape (3, 2)
rank 8 has local_ary with shape (3, 2)
rank 6 has local_ary with shape (3, 2)

以上介绍了 mpi4py 中获得高性能 I/O 的方法和建议,在下一篇中我们将介绍 mpi4py 并行读/写 numpy npy 文件的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容