mpi4py 中讀/寫文件中數(shù)組的方法

上一篇中我們介紹了 mpi4py 中的不連續(xù)讀/寫和集合 I/O 操作,下面我們將介紹 mpi4py 中讀/寫文件中數(shù)組的方法。

在并行科學(xué)計(jì)算程序中,經(jīng)常會(huì)涉及到讀/寫文件中的數(shù)組(包括子數(shù)組和分布式數(shù)組,數(shù)組可以是多維的,規(guī)則分布或不規(guī)則分布的)。MPI 提供了相應(yīng)的方法使這種類型的操作方便而高效。

在并行應(yīng)用中,數(shù)組一般會(huì)按照某種方式分布在多個(gè)進(jìn)程中,而程序需要將這些分布在各個(gè)進(jìn)程中的數(shù)組按照整體的行優(yōu)先順序(C 數(shù)組的排列方式)或列優(yōu)先順序(Fortran 數(shù)組的排列方式)寫入到文件中,或者從文件中將一個(gè)整體的數(shù)組讀取并分布到各個(gè)進(jìn)程上。比如像下圖所示,一個(gè)兩維的 m 行 n 列的數(shù)組分布在一個(gè) 2 × 3 的進(jìn)程網(wǎng)格上。

m × n 數(shù)組分布在 2 × 3 的進(jìn)程網(wǎng)格上

如果要將這些進(jìn)程中的數(shù)據(jù)按照行優(yōu)先的排列方法整體地寫入到一個(gè)文件中,則可以看出某個(gè)進(jìn)程本地的子數(shù)組并不是連續(xù)地位于文件中的某塊區(qū)域,因此我們必須執(zhí)行一種不連續(xù)的數(shù)據(jù)寫過(guò)程,我們可以利用上一篇所介紹的相關(guān)方法。因?yàn)轭愃七@樣的數(shù)據(jù)讀/寫操作是如此的普遍,而要自己創(chuàng)建描述這種操作的數(shù)據(jù)類型又是麻煩而且容易出錯(cuò)的,因此 MPI 提供了相應(yīng)的數(shù)據(jù)描述方法供我們方便地使用。利用這些數(shù)據(jù)描述方法并結(jié)合集合 I/O 操作,往往可以允許我們通過(guò)僅僅一次讀/寫調(diào)用完成對(duì)這個(gè)分布式數(shù)組的讀/寫操作,并且 MPI 實(shí)現(xiàn)可能提供高的優(yōu)化性能,盡管執(zhí)行的是非連續(xù)的讀/寫。下面對(duì)執(zhí)行數(shù)據(jù)描述方法做簡(jiǎn)要的介紹,用這些數(shù)據(jù)描述方法創(chuàng)建的數(shù)據(jù)類型可以作為文件視圖的 filetype 以實(shí)現(xiàn)對(duì)數(shù)組的方便而高效的讀/寫操作。

分布式數(shù)組

分布式數(shù)組數(shù)據(jù)描述方法是一種方便易用的描述一個(gè)線性化的規(guī)則多維數(shù)組中一個(gè)子數(shù)組位置的派生數(shù)據(jù)類型創(chuàng)建方法,其方法接口如下:

MPI.Datatype.Create_darray(self, int size, int rank, gsizes, distribs, dargs, psizes, int order=ORDER_C)

該方法在前面數(shù)據(jù)類型創(chuàng)建方法中作過(guò)相應(yīng)的介紹,這里不再贅述,只強(qiáng)調(diào)幾點(diǎn):在創(chuàng)建分布式數(shù)組派生數(shù)據(jù)類型時(shí),進(jìn)程網(wǎng)格總是假定與整體數(shù)組有著相同數(shù)目的維數(shù),如果整體數(shù)組在某個(gè)維度上不分布,進(jìn)程網(wǎng)格也不能省略掉該維度,而必須設(shè)置該維度上的進(jìn)程數(shù)為 1。比如說(shuō),一個(gè) 10 × 10 的整體數(shù)組分布在 4 個(gè)進(jìn)程上,則這 4 個(gè)進(jìn)程可以排成 2 × 2 的進(jìn)程網(wǎng)格,1 × 4 進(jìn)程網(wǎng)格,或是 4 × 1 進(jìn)程網(wǎng)格。進(jìn)程網(wǎng)格必須總是按照行優(yōu)先的順序(即 C 數(shù)組排列順序)。如果程序中要使用一種不同的進(jìn)程網(wǎng)格排列順序,則不能使用該數(shù)據(jù)類型創(chuàng)建方法,可以考慮下面介紹的子數(shù)組類型創(chuàng)建方法或其它的派生數(shù)據(jù)類型創(chuàng)建方法。

子數(shù)組

子數(shù)組數(shù)據(jù)類型描述方法是另一種描述一個(gè)線性化的規(guī)則多維數(shù)組中一個(gè)子數(shù)組位置的派生數(shù)據(jù)類型創(chuàng)建方法,其方法接口如下:

MPI.Datatype.Create_subarray(self, sizes, subsizes, starts, int order=ORDER_C)

該方法在前面數(shù)據(jù)類型創(chuàng)建方法中作過(guò)相應(yīng)的介紹,這里不再贅述,只強(qiáng)調(diào)幾點(diǎn):子數(shù)組數(shù)據(jù)類型創(chuàng)建方法只能描述塊狀分布的子數(shù)組,而不能像上面介紹的分布式數(shù)組創(chuàng)建方法那樣描述循環(huán)分布及更普遍的塊狀循環(huán)分布。另外,子數(shù)組數(shù)據(jù)類型創(chuàng)建方法對(duì)進(jìn)程的拓?fù)漤樞驔](méi)有要求,可以是列優(yōu)先的順序,還可以是其它任何排列順序。一般為了方便,可以使用虛擬進(jìn)程拓?fù)浞椒?/a>首先按照某種虛擬拓?fù)浣Y(jié)構(gòu)安排好相應(yīng)的進(jìn)程。

子數(shù)組數(shù)據(jù)類型描述方法也能很好地用來(lái)描述一類帶有 ghost area 的分布式數(shù)組。在一些應(yīng)用中,分布于某個(gè)進(jìn)程的本地?cái)?shù)組在一些維度上會(huì)包含幾個(gè)額外的行或列,這些額外的區(qū)域,并不是該進(jìn)程的本地?cái)?shù)組的實(shí)際部分,通常被稱做 halo 或者 ghost area。這些 ghost area 一般是用來(lái)存儲(chǔ)鄰近進(jìn)程的行或列以利于該進(jìn)程和鄰近進(jìn)程之間的通信及便于對(duì)本地?cái)?shù)組的相關(guān)操作。下圖給出了一個(gè)帶有 ghost area 的本地?cái)?shù)組的例子。這個(gè)本地?cái)?shù)組實(shí)際上只有 100 行 100 列,但是在其外圍包裹了 4 行或 4 列的 ghost area,使其變成了一個(gè) 108 行 108 列的數(shù)組??梢钥闯?,在這個(gè)帶有 ghost area 的本地?cái)?shù)組中,處于中心部位的實(shí)際數(shù)組在內(nèi)存中的排布也是不連續(xù)的。另外,分布在各個(gè)進(jìn)程中的數(shù)組作為一個(gè)整體如果要寫入到文件或從文件讀入各個(gè)進(jìn)程,也會(huì)涉及不連續(xù)的讀/寫過(guò)程。在這種情況下,數(shù)據(jù)的 I/O 操作在內(nèi)存和文件中都是不連續(xù)的,但是借助子數(shù)組數(shù)據(jù)類型及集合 I/O 讀寫方法,我們依然可以通過(guò)一次讀/寫調(diào)用完成相應(yīng)的操作。

帶有 ghost area 的本地?cái)?shù)組

不規(guī)則分布式數(shù)組

MPI 也提供了方法來(lái)讀/寫不規(guī)則的分布式數(shù)組,只要使用合適的 filetype 來(lái)設(shè)置文件視圖即可。如果和集合 I/O 方法結(jié)合起來(lái)使用,MPI 實(shí)現(xiàn)也可能以高的性能完成對(duì)這類數(shù)據(jù)的讀/寫操作,雖然這類讀/寫操作一般認(rèn)為是很難優(yōu)化的。不規(guī)則的分布是指不能很好地用簡(jiǎn)單的數(shù)學(xué)表達(dá)的分布形式,不像一般的塊狀或者循環(huán)分布那樣有規(guī)律性。對(duì)這類操作,我們需要使用一種類型映射圖方式來(lái)描述進(jìn)程本地?cái)?shù)組中的每一個(gè)元素與整體數(shù)組元素之間的映射關(guān)系。下圖中就給出了這種類型映射圖的一個(gè)例子,映射圖中的每一個(gè)元素指定本地?cái)?shù)組中位于該處的元素對(duì)應(yīng)文件中的元素的位置,比如說(shuō),0 號(hào)進(jìn)程本地?cái)?shù)組的第 0,1,2,3 個(gè)元素分別對(duì)應(yīng)文件中的第 0,3,8,11 個(gè)元素,1 號(hào)進(jìn)程本地?cái)?shù)組的第 0,1,2,3 個(gè)元素分別對(duì)應(yīng)文件中的第 2,4,7,13 個(gè)元素,等等。

類型映射圖

這是一種比上面介紹的分布式數(shù)組和子數(shù)組數(shù)據(jù)描述方法更加通用的數(shù)據(jù)類型描述方法。不過(guò)使用這種數(shù)據(jù)描述方法需要注意的是,MPI 標(biāo)準(zhǔn)規(guī)定每個(gè)進(jìn)程用來(lái)設(shè)置文件視圖的 filetype 只能描述文件中單調(diào)非減的偏移位置。但是對(duì)內(nèi)存中的數(shù)據(jù)類型描述則沒(méi)有此限制。因此在一些情況下,我們可能需要將對(duì)應(yīng)內(nèi)存中的數(shù)據(jù)描述做適當(dāng)?shù)恼{(diào)整和重新排列,以保持設(shè)置文件視圖的 filetype 中的偏移位置是單調(diào)非減的。創(chuàng)建這種對(duì)不規(guī)則分布式數(shù)組描述的數(shù)據(jù)類型的相關(guān)方法接口如下:

MPI.Datatype.Create_indexed(self, blocklengths, displacements)

MPI.Datatype.Create_hindexed(self, blocklengths, displacements)

MPI.Datatype.Create_indexed_block(self, int blocklength, displacements)

MPI.Datatype.Create_hindexed_block(self, int blocklength, displacements)

這些方法在前面數(shù)據(jù)類型創(chuàng)建方法中作過(guò)相應(yīng)的介紹,這里不再贅述。

例程

下面給出使用例程。

# array_io.py

"""
Demonstrates how to access arrays stored in file.

Run this with 6 processes like:
$ mpiexec -n 6 python array_io.py
"""

import numpy as np
from mpi4py import MPI


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

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

# the global array
global_ary = np.arange(10*12, dtype='i').reshape(10, 12)
rs, re = 5*ri, 5*(ri+1) # start and end of row
cs, ce = 4*ci, 4*(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'

# -------------------------------------------------------------------------------
# use darray type

# 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)

# the etype
etype = MPI.INT

# construct filetype
gsizes = [10, 12] # global shape of the array
distribs = [MPI.DISTRIBUTE_BLOCK, MPI.DISTRIBUTE_BLOCK] # block distribution in both dimensions
dargs = [MPI.DISTRIBUTE_DFLT_DARG, MPI.DISTRIBUTE_DFLT_DARG] # default distribution args
psizes = [2, 3] # process grid in C order
filetype = MPI.INT.Create_darray(6, rank, gsizes, distribs, dargs, psizes)
filetype.Commit()

# set the file view
fh.Set_view(0, etype, filetype)

# collectively write the data to file
fh.Write_all(local_ary)

# reset file view
fh.Set_view(0, etype, etype)

# check what's in the file
if rank == 0:
    buf = np.zeros(10 * 12, dtype='i').reshape(10, 12)
    fh.Read_at(0, buf)
    assert np.allclose(buf, global_ary)

# close the file
fh.Close()


# -------------------------------------------------------------------------------
# use subarray type

# 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)

# the etype
etype = MPI.INT

# construct filetype
gsizes = [10, 12] # global shape of the array
subsize = [5, 4] # 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()

# set the file view
fh.Set_view(0, etype, filetype)

# collectively write the data to file
fh.Write_all(local_ary)

# reset file view
fh.Set_view(0, etype, etype)

# check what's in the file
if rank == 0:
    buf = np.zeros(10 * 12, dtype='i').reshape(10, 12)
    fh.Read_at(0, buf)
    assert np.allclose(buf, global_ary)

# close the file
fh.Close()


# -------------------------------------------------------------------------------
# use subarray type to access local array with ghost area

# create local array with one row and one column ghost area outside
local_ghost = np.zeros((7, 6), dtype='i')
local_ghost[1:6, 1:5] = local_ary # put local_ary in the center of local_ghost
# here you can fill in the ghost area, but data in ghost area will not be writen
# to file, so we omit it here...

# 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)

# the etype
etype = MPI.INT

# construct filetype
gsizes = [10, 12] # global shape of the array
subsize = [5, 4] # 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()

# set the file view
fh.Set_view(0, etype, filetype)

# create a subarray type to describe the data located in local_phost without ghost area
memsize = local_ghost.shape
subsize = local_ary.shape
starts  = [1, 1]
memtype = MPI.INT.Create_subarray(memsize, subsize, starts)
memtype.Commit()

# collectively write the actual data inside local_ghost to file
fh.Write_all([local_ghost, 1, memtype])

# reset file view
fh.Set_view(0, etype, etype)

# check what's in the file
if rank == 0:
    buf = np.zeros(10 * 12, dtype='i').reshape(10, 12)
    fh.Read_at(0, buf)
    assert np.allclose(buf, global_ary)

# close the file
fh.Close()


# -------------------------------------------------------------------------------
# use map array to access irregularly distributed array

global_ary = np.arange(100, 124, dtype='i')
index_ary = np.arange(4*6, dtype='i')
# permutate the index array
if rank == 0:
    rand_index = np.random.permutation(index_ary)
else:
    rand_index = None
rand_index = comm.bcast(rand_index, root=0)
map_ary = np.sort(rand_index[4*rank:4*(rank+1)]) # map array should be nondecreasing
local_ary = global_ary[map_ary]
if rank == 0:
    print 'global_ary: %s' % global_ary
print 'rank %d has local_ary: %s, map_ary: %s' % (rank, local_ary, map_ary)

# 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)

# the etype
etype = MPI.INT

# construct filetype
filetype = MPI.INT.Create_indexed_block(1, displacements=map_ary)
filetype.Commit()

# set the file view
fh.Set_view(0, etype, filetype)

# collectively write the data to file
fh.Write_all(local_ary)

# reset file view
fh.Set_view(0, etype, etype)

# check what's in the file
if rank == 0:
    buf = np.zeros(24, dtype='i')
    fh.Read_at(0, buf)
    assert np.allclose(buf, global_ary)

# close the file
fh.Close()

運(yùn)行結(jié)果如下:

$ mpiexec -n 6 python array_io.py
rank 0 has local_ary with shape (5, 4)
rank 1 has local_ary with shape (5, 4)
rank 2 has local_ary with shape (5, 4)
rank 3 has local_ary with shape (5, 4)
rank 4 has local_ary with shape (5, 4)
rank 5 has local_ary with shape (5, 4)
rank 4 has local_ary: [100 107 113 118], map_ary: [ 0  7 13 18]
rank 5 has local_ary: [103 111 116 117], map_ary: [ 3 11 16 17]
global_ary: [100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
118 119 120 121 122 123]
rank 0 has local_ary: [108 109 110 120], map_ary: [ 8  9 10 20]
rank 1 has local_ary: [102 104 114 115], map_ary: [ 2  4 14 15]
rank 2 has local_ary: [105 106 119 121], map_ary: [ 5  6 19 21]
rank 3 has local_ary: [101 112 122 123], map_ary: [ 1 12 22 23]

以上介紹了 mpi4py 中讀/寫文件中數(shù)組的方法,在下一篇中我們將介紹 mpi4py 中的非阻塞 I/O 操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容