在上一篇中我們介紹了 MPI-3 中引進的非阻塞通信子複製和組集合通信子創建方法,下面我們將介紹 MPI-3 中增強的單邊通信方法。
MPI-2 引進了單邊通信,也稱遠端內存訪問(Remote Memory Access,RMA)。單邊通信操作可以提供更高的性能(在有好的硬件支撐的情況下)和更強的功能,簡化某些並行編程任務。在前面我們介紹過 MPI-2 引進的單邊通信的相關概念和操作方法。MPI-3 極大地擴展了 MPI-2 的單邊通信功能,澄清了一些比較模糊的概念,並且解決了限制 MPI-2 的單邊通信廣泛應用的一些限制。
方法接口
下面給出 MPI-3 中擴展的單邊通信相關方法(MPI.Win 類方法)。
窗口創建
Create_dynamic(type cls, Info info=INFO_NULL, Intracomm comm=COMM_SELF)
創建並返回用於單邊通信的窗口對象,返回的窗口對象沒有爲其分配的內存。在組內通信子 comm
所指定的通信子範圍內所有進程上執行集合操作。info
對象用於爲 MPI 環境提供優化所需的輔助信息,可用的 key 有:
- no_locks:如果爲 True,會假定所創建的窗口不會被加鎖(即調用 MPI.Win.Lock 或者 MPI.Win.Lock_all)。如果只使用主動同步,則可以設定此 key 爲 True,此時 MPI 實現可對單邊操作做一些優化。
- accumulate_ordering:默認情況下對同一個窗口的多個 Accumulate 操作會按照嚴格的調用順序執行。如果將此 key 設置成 none,則 MPI 實現不會保證對同一個窗口的多個 Accumulate 操作的執行順序。該 key 也可以設置成一系列由逗號分隔的 rar (read-after-read), war (write-after-read), raw (read-after-write) 和 waw (write-after-write) 以指明相應的執行順序。放鬆對執行順序的限制可能會提高執行效率。
accumulate_ops:如果設置成 same_op,MPI 實現會假定對窗口的同一目的地址的併發 Accumulate 調用都會使用同樣的算符。如果設置成 same_op_no_op,MPI 實現會假定對窗口的同一目的地址的併發 Accumulate 調用都會使用同樣的算符或者 MPI.NO_OP。該 key 的默認值爲 same_op_no_op。
Allocate(type cls, Aint size, int disp_unit=1, Info info=INFO_NULL, Intracomm comm=COMM_SELF)
分配指定大小的內存並創建和返回用於單邊通信的窗口對象。在組內通信子 comm
所指定的通信子範圍內所有進程上執行集合操作。每個進程所返回的窗口對象會包含一塊分配好的 size
大小的內存。每個進程的 size
可以不同,甚至可以爲 0。disp_unit
指定在遠端內存訪問操作中的地址單位,即 origin 所指定的位置在 target 一側要以 target 進程所指定的 diap_unit
爲單位計算。通常如果採用相同類型創建窗口,則統一將 disp_unit
設置成 1 即可。如果有的進程需要以組合數據類型(type)給出緩衝區,則可能需要指定 disp_unit
爲 sizeof(type)。info
對象用於爲 MPI 環境提供優化所需的輔助信息。
Allocate_shared(type cls, Aint size, int disp_unit=1, Info info=INFO_NULL, Intracomm comm=COMM_SELF)
創建並返回一個擁有共享內存的窗口對象。MPI-3 共享內存相關操作將在下一篇中介紹。
Attach(self, memory)
給當前窗口對象對象(由 Create_dynamic 所創建的窗口對象)加載一塊內存 memory
。
Detach(self, memory)
卸載當前窗口對象(由 Create_dynamic 所創建的窗口對象)的內存 memory
。
單邊操作
Rput(self, origin, int target_rank, target=None)
對應 MPI.Win.Put 操作,參數也同 MPI.Win.Put,不同的是該方法返回一個 MPI.Request 對象,該對象的完成(可通過 Wait,Test 等)意味着發送的進程可以更改其 origin
數據緩衝區,但並不表示目標進程的窗口已經完成了數據的接收(需要通過 MPI.Win.Flush,MPI.Win.Flush_all,MPI.Win.Unlock,MPI.Win.Unlock_all 等來完成)。
Rget(self, origin, int target_rank, target=None)
對應 MPI.Win.Get 操作,參數也同 MPI.Win.Get,不同的是該方法返回一個 MPI.Request 對象,該對象的完成(可通過 Wait,Test 等)意味着 origin
緩衝區已經從遠端拿到數據。
Raccumulate(self, origin, int target_rank, target=None, Op op=SUM)
對應 MPI.Win.Accumulate 操作,參數也同 MPI.Win.Accumulate,不同的是該方法返回一個 MPI.Request 對象,該對象的完成(可通過 Wait,Test 等)意味着發送的進程可以更改其 origin
數據緩衝區,但並不表示目標進程的窗口已經完成了數據的更新(需要通過 MPI.Win.Flush,MPI.Win.Flush_all,MPI.Win.Unlock,MPI.Win.Unlock_all 等來完成)。
Get_accumulate(self, origin, result, int target_rank, target=None, Op op=SUM)
該操作將 origin
中的數據用 Reduce 中所定義的操作 op
更新 target_rank
的窗口緩衝區中由 target
所指定位置處的數據,並通過 result
返回target_rank
的窗口緩衝區中未被 accumulate 更新前的數據。除 result
外,其它參數同 MPI.Accumulate 方法。op
可以使用 Reduce 內置定義的操作,MPI.REPLACE 和 MPI.NO_OP,但是不能使用用戶自定義的算符。操作的數據只能是預定義的數據類型或由同一種預定義數據類型創建的用戶自定義數據類型。
Rget_accumulate(self, origin, result, int target_rank, target=None, Op op=SUM)
對應 MPI.Win.Get_accumulate 操作,參數也同 MPI.Win.Get_accumulate,不同的是該方法返回一個 MPI.Request 對象,該對象的完成(可通過 Wait,Test 等)意味着發送的進程可以更改其 origin
數據緩衝區,並且 result
已經拿到數據,但並不表示目標進程的窗口已經完成了數據的更新(需要通過 MPI.Win.Flush,MPI.Win.Flush_all,MPI.Win.Unlock,MPI.Win.Unlock_all 等來完成)。
Fetch_and_op(self, origin, result, int target_rank, Aint target_disp=0, Op op=SUM)
是 Get_accumulate 的簡化版本,只能操作單個數據項(由操作的數據類型定義),除 target_disp
外其它參數與 Get_accumulate 的對應參數同。target_disp
指定要操作的遠程窗口的起始偏移位置。
Compare_and_swap(self, origin, compare, result, int target_rank, Aint target_disp=0)
將 compare
中的數據與 target_rank
窗口緩衝區中偏移 target_disp
處的單個數據項進行比較,如果相同則用 origin
中的數據替換target_rank
窗口緩衝區中偏移 target_disp
處的數據,並由 result
返回未被替換前的數據。操作的數據類型只能是以下幾種:C 整型、Fortran 整型、布爾型、Byte 型等。
同步操作
Lock_all(self, int assertion=0)
對該窗口對象中的所有進程啓動一個訪問時間段,加鎖的類型爲 MPI.LOCK_SHARED(共享鎖)。在此訪問時間段內調用此方法的進程可以通過單邊通信操作訪問該窗口對象的所有進程的窗口緩衝區。必須用配對的 Unlock_all 來解鎖。該方法不是一個集合操作,ALL 是指該窗口包含的所有進程。assertion
除了默認的 0 之外可以設置爲 MPI.MODE_NOCHECK,表示在嘗試創建鎖時,可以確信沒有其它進程已經取得了相同窗口對象的鎖,或者正在嘗試獲取窗口對象的鎖。
Unlock_all(self)
與 Lock_all 配對,標記一個訪問時間段的結束。該方法返回後在該訪問時間段內的所有單邊通信操作的源端和目的端都會完成。
Flush(self, int rank)
該方法完成調用進程對 rank
進程的所有單邊訪問操作,源端和目的端都會完成。只能用在被動同步的訪問時間段內。
Flush_all(self)
該方法完成調用進程的所有單邊訪問操作,源端和目的端都會完成。只能用在被動同步的訪問時間段內。
Flush_local(self, int rank)
該方法完成調用進程對 rank
進程的所有單邊訪問操作的本地源端緩衝區。該方法返回後當前進程就可以重新使用提供給 Put,Get,Accumulate 方法的 origin
緩衝區。只能用在被動同步的訪問時間段內。
Flush_local_all(self)
該方法完成調用進程的所有單邊訪問操作的本地源端緩衝區。該方法返回後當前進程就可以重新使用提供給 Put,Get,Accumulate 方法的 origin
緩衝區。只能用在被動同步的訪問時間段內。
Sync(self)
同步當前窗口的私有和公共內存區。它並不會完成一些尚未完成的單邊通信操作。
例程
下面給出上面介紹的部分方法的使用例程。
# mpi3_rma.py
"""
Demonstrates the usage of MPI-3 enhanced RMA.
Run this with 4 processes like:
$ mpiexec -n 4 python mpi3_rma.py
"""
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
# Create
if rank == 0:
win = MPI.Win.Create(None, comm=comm)
# Lock_all
win.Lock_all()
for rk in [1, 2, 3]:
a = np.array([rk, rk], dtype='i')
# Put
win.Put(a, target_rank=rk)
print 'rank %d put %s to rank %d' % (rank, a, rk)
# Unlock_all
win.Unlock_all()
# Lock for rank 1
win.Lock(rank=1)
b = np.array([10, 10], dtype='i')
c = np.array([-1, -1], dtype='i')
# Get_accumulate
win.Get_accumulate(b, c, target_rank=1, op=MPI.SUM)
# Unlock for rank 1
win.Unlock(rank=1)
print 'rank %d Get_accumulate %s to rank 1, and get result %s' % (rank, b, c)
comm.Barrier()
else:
mem = np.array([-1, -1], dtype='i')
win = MPI.Win.Create(mem, comm=comm)
comm.Barrier()
print 'rank %d get %s' % (rank, mem)
# Allocate
if rank == 0:
win = MPI.Win.Allocate(0, disp_unit=4, comm=comm)
# Lock_all
win.Lock_all()
reqs = []
for rk in [1, 2, 3]:
a = np.array([rk, rk], dtype='i')
# Rput
req = win.Rput(a, target_rank=rk)
reqs.append(req)
print 'rank %d put %s to rank %d' % (rank, a, rk)
# compute all Rput
MPI.Request.Waitall(reqs)
# Unlock_all
win.Unlock_all()
comm.Barrier()
else:
win = MPI.Win.Allocate(8, disp_unit=4, comm=comm)
comm.Barrier()
# convert the memory of win to numpy array
buf = np.array(buffer(win.tomemory()), dtype='B', copy=False)
mem = np.ndarray(buffer=buf, dtype='i', shape=(2,))
print 'rank %d get %s' % (rank, mem)
# Create_dynamic
win = MPI.Win.Create_dynamic(comm=comm)
mem = MPI.Alloc_mem(8)
# Attach and Detach
win.Attach(mem)
win.Detach(mem)
MPI.Free_mem(mem)
運行結果如下:
$ mpiexec -n 4 python mpi3_rma.py
rank 0 put [1 1] to rank 1
rank 0 put [2 2] to rank 2
rank 0 put [3 3] to rank 3
rank 0 Get_accumulate [10 10] to rank 1, and get result [1 1]
rank 1 get [11 11]
rank 2 get [2 2]
rank 3 get [3 3]
rank 0 put [1 1] to rank 1
rank 0 put [2 2] to rank 2
rank 0 put [3 3] to rank 3
rank 3 get [3 3]
rank 1 get [1 1]
rank 2 get [2 2]
以上介紹了 MPI-3 中增強的單邊通信方法,在下一篇中我們將介紹 MPI-3 中共享內存操作。