在上一篇中我們概要地介紹了最新的 MPI-3 標(biāo)準(zhǔn)中引進(jìn)的新特性,mpi4py 3.0.0 支持 MPI-3 的很多新特性梨州,我們將在后面逐步介紹痕囱,下面我們首先介紹 mpi4py 中的非阻塞集合通信。
在前面我們介紹了 mpi4py 中的集合通信暴匠,不過前面介紹的是 MPI-1 和 MPI-2 標(biāo)準(zhǔn)下的集合通信方法鞍恢,所有這些方法都是阻塞式的,在通信沒有完成之前巷查,這些方法不會(huì)返回有序,因此無法進(jìn)行后面的計(jì)算任務(wù)。MPI-3 標(biāo)準(zhǔn)引進(jìn)了所有這些集合通信方法的非阻塞版本岛请,mpi4py 中的非阻塞集合通信方法與其對(duì)應(yīng)的阻塞版本有著完全一樣的方法接口旭寿,不同的是這些非阻塞方法會(huì)返回一個(gè) MPI.Request 對(duì)象,然后可以通過該對(duì)象的 Test 或者 Wait 等方法來測(cè)試或等待通信的完成崇败,這部分與非阻塞的點(diǎn)到點(diǎn)通信的測(cè)試和等待是一樣的盅称。非阻塞集合通信也可以防止死鎖,并通過將通信和計(jì)算重疊而提高程序的運(yùn)行效率后室。
方法接口
下面給出非阻塞集合通信的方法接口缩膝。
MPI.Comm.Ibcast(self, buf, int root=0)
非阻塞廣播操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Bcast岸霹,返回 MPI.Request 對(duì)象疾层。
MPI.Comm.Iscatter(self, sendbuf, recvbuf, int root=0)
非阻塞發(fā)散操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Scatter贡避,返回 MPI.Request 對(duì)象痛黎。
MPI.Comm.Iscatterv(self, sendbuf, recvbuf, int root=0)
非阻塞向量發(fā)散操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Scatterv刮吧,返回 MPI.Request 對(duì)象湖饱。
MPI.Comm.Igather(self, sendbuf, recvbuf, int root=0)
非阻塞收集操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Gather杀捻,返回 MPI.Request 對(duì)象井厌。
MPI.Comm.Igatherv(self, sendbuf, recvbuf, int root=0)
非阻塞向量收集操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Gatherv致讥,返回 MPI.Request 對(duì)象仅仆。
MPI.Comm.Ireduce(self, sendbuf, recvbuf, Op op=SUM, int root=0)
非阻塞規(guī)約操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Reduce拄踪,返回 MPI.Request 對(duì)象蝇恶。
MPI.Comm.Iallgather(self, sendbuf, recvbuf)
非阻塞全收集操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Allgather惶桐,返回 MPI.Request 對(duì)象撮弧。
MPI.Comm.Iallgatherv(self, sendbuf, recvbuf)
非阻塞向量全收集操作潘懊。對(duì)應(yīng)阻塞版本的 MPI.Comm.Allgatherv,返回 MPI.Request 對(duì)象贿衍。
MPI.Comm.Iallreduce(self, sendbuf, recvbuf, Op op=SUM)
非阻塞全規(guī)約操作授舟。對(duì)應(yīng)阻塞版本的 MPI.Comm.Allreduce,返回 MPI.Request 對(duì)象贸辈。
MPI.Comm.Ireduce_scatter_block(self, sendbuf, recvbuf, Op op=SUM)
非阻塞非向量規(guī)約發(fā)散操作释树。對(duì)應(yīng)阻塞版本的 MPI.Comm.Reduce_scatter_block,返回 MPI.Request 對(duì)象擎淤。
MPI.Comm.Ireduce_scatter(self, sendbuf, recvbuf, recvcounts=None, Op op=SUM)
非阻塞向量規(guī)約發(fā)散操作奢啥。對(duì)應(yīng)阻塞版本的 MPI.Comm.Reduce_scatter,返回 MPI.Request 對(duì)象嘴拢。
MPI.Comm.Ialltoall(self, sendbuf, recvbuf)
非阻塞全發(fā)散操作桩盲。對(duì)應(yīng)阻塞版本的 MPI.Comm.Alltoall,返回 MPI.Request 對(duì)象席吴。
MPI.Comm.Ialltoallv(self, sendbuf, recvbuf)
非阻塞向量全發(fā)散操作赌结。對(duì)應(yīng)阻塞版本的 MPI.Comm.Alltoallv,返回 MPI.Request 對(duì)象孝冒。
MPI.Comm.Ialltoallw(self, sendbuf, recvbuf)
非阻塞向量全發(fā)散操作柬姚。對(duì)應(yīng)阻塞版本的 MPI.Comm.Alltoallw,返回 MPI.Request 對(duì)象庄涡。
MPI.Intracomm.Iscan(self, sendbuf, recvbuf, Op op=SUM)
非阻塞掃描操作量承。對(duì)應(yīng)阻塞版本的 MPI.Comm.Scan,返回 MPI.Request 對(duì)象穴店。注意:此方法只在組內(nèi)通信子上有定義宴合。
MPI.Intracomm.Iexscan(self, sendbuf, recvbuf, Op op=SUM)
非阻塞前綴掃描操作。對(duì)應(yīng)阻塞版本的 MPI.Comm.Exscan迹鹅,返回 MPI.Request 對(duì)象。注意:此方法只在組內(nèi)通信子上有定義贞言。
MPI.Comm.Ibarrier(self)
非阻塞柵障同步操作斜棚。對(duì)應(yīng)阻塞版本的 MPI.Comm.Barrier,返回 MPI.Request 對(duì)象该窗。
例程
下面給出部分非阻塞集合操作的使用例程弟蚀。
# nbc.py
"""
Demonstrates nonblocking collective communication.
Run this with 4 processes like:
$ mpiexec -n 4 python nbc.py
"""
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
# ------------------------------------------------------------------------
# broadcast a numpy array by using Ibcast
if rank == 0:
ary = np.arange(10, dtype='i')
else:
ary = np.empty(10, dtype='i')
req = comm.Ibcast(ary, root=0)
req.Wait()
print 'Ibcast: rank %d has %s' % (rank, ary)
# ------------------------------------------------------------------------
# scatter a numpy array by using Iscatterv
if rank == 0:
send_buf = np.arange(10, dtype='i')
recv_buf = np.empty(4, dtype='i')
elif rank == 1:
send_buf = None
recv_buf = np.empty(3, dtype='i')
elif rank == 2:
send_buf = None
recv_buf = np.empty(2, dtype='i')
else:
send_buf = None
recv_buf = np.empty(1, dtype='i')
count = [4, 3, 2, 1]
displ = [0, 4, 7, 9]
req = comm.Iscatterv([send_buf, count, MPI.INT], recv_buf, root=0)
req.Wait()
print 'Iscatterv: rank %d has %s' % (rank, recv_buf)
# ------------------------------------------------------------------------
# Ialltoall
send_buf = np.arange(8, dtype='i')
recv_buf = np.empty(8, dtype='i')
req = comm.Ialltoall(send_buf, recv_buf)
req.Wait()
print 'Ialltoall: rank %d has %s' % (rank, recv_buf)
運(yùn)行結(jié)果如下:
$ mpiexec -n 4 python nbc.py
Ibcast: rank 0 has [0 1 2 3 4 5 6 7 8 9]
Iscatterv: rank 0 has [0 1 2 3]
Ialltoall: rank 0 has [0 1 0 1 0 1 0 1]
Ibcast: rank 1 has [0 1 2 3 4 5 6 7 8 9]
Iscatterv: rank 1 has [4 5 6]
Ialltoall: rank 1 has [2 3 2 3 2 3 2 3]
Ibcast: rank 2 has [0 1 2 3 4 5 6 7 8 9]
Iscatterv: rank 2 has [7 8]
Ialltoall: rank 2 has [4 5 4 5 4 5 4 5]
Ibcast: rank 3 has [0 1 2 3 4 5 6 7 8 9]
Iscatterv: rank 3 has [9]
Ialltoall: rank 3 has [6 7 6 7 6 7 6 7]
非阻塞柵障同步——Ibarrier
非阻塞集合通信方法除 Ibarrier 外都比較容易理解,因?yàn)樗鼈兺鋵?duì)應(yīng)的阻塞版本有著同樣的語義酗失。但是非阻塞的柵障同步操作 Ibarrier 卻讓人感到有點(diǎn)匪夷所思义钉。因?yàn)?Ibarrier 是非阻塞的,并不會(huì)讓運(yùn)行快的進(jìn)程停在此等待其它進(jìn)程规肴,表面上看似乎起不到讓進(jìn)程同步的作用捶闸。實(shí)際上非阻塞的柵障同步 Ibarrier 是一個(gè)非常有用的操作夜畴,合適地使用它可以大大提高程序的計(jì)算效率。Ibarrier 雖然不會(huì)讓進(jìn)程阻塞下來等待删壮,但是到達(dá) Ibarrier 的進(jìn)程會(huì)宣告自己已經(jīng)到達(dá)了需要同步的執(zhí)行點(diǎn)贪绘,然后可以去做其它不依賴于此同步的工作,而不必像在阻塞同步中那樣在此白白等待央碟,其可以周期性地通過 Test 等方法來檢測(cè)是否所有的進(jìn)程都已經(jīng)到達(dá)需要同步的點(diǎn)税灌,只要還有進(jìn)程沒有到達(dá)該點(diǎn),Test 的結(jié)果就會(huì)為 False亿虽,一旦所有進(jìn)程都到達(dá)了同步點(diǎn)菱涤,Test 的結(jié)果就會(huì)為 True,表示完成了所需的同步工作洛勉,所有進(jìn)程可以進(jìn)行后續(xù)依賴于此同步的計(jì)算任務(wù)粘秆。
打個(gè)簡(jiǎn)單的比方,幾個(gè)人商量好 9 點(diǎn)在某會(huì)議室開會(huì)坯认,只有等所有人都到齊后才會(huì)舉行會(huì)議翻擒,有的人可能會(huì)早于 9 點(diǎn)到達(dá),有的人可能會(huì)遲到牛哺,在阻塞同步的情況下陋气,先到的人都會(huì)在會(huì)議室什么事都不做白白等著最后一個(gè)到的人,而在非阻塞同步情況下引润,先到的人可以在會(huì)議室簽個(gè)到或留張小紙條以表明自己已經(jīng)到了巩趁,然后可以到附近溜達(dá)溜達(dá),比如說喝杯咖啡或做點(diǎn)其它與會(huì)議無關(guān)的事情淳附,并每過一段時(shí)間回來看看簽到表或小紙條是否所有人都已到齊议慰,只要還有人沒到,就可以接著做點(diǎn)自己的事情奴曙,一旦所有人都已到齊别凹,會(huì)議就可以舉行了。
下面給出非阻塞柵障同步操作的簡(jiǎn)單示例洽糟。
# Ibarrier.py
"""
Demonstrates the usage of Ibarrier()
Run this with 4 processes like:
$ mpiexec -n 4 python Ibarrier.py
"""
import time
import random
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
# synchronize here by blocking barrier
comm.Barrier()
# each process sleep for a random of time
time.sleep(random.random() / 100000)
# nonblocking barrier
req = comm.Ibarrier()
cnt = 0
while(not req.Test()):
# do some work until all processes reach the Ibarrier
print 'rank %d: %d' % (rank, cnt)
cnt += 1
# do other things depend on this Ibarrier
# ...
運(yùn)行結(jié)果如下:
$ mpiexec -n 4 python Ibarrier.py
rank 3: 0
rank 0: 0
rank 0: 1
rank 0: 2
rank 0: 3
rank 1: 0
rank 2: 0
rank 2: 1
rank 2: 2
在以上例程中炉菲,由于每個(gè)進(jìn)程 sleep 的時(shí)間不同,因此到達(dá) Ibarrier 的時(shí)間也不同坤溃,但是因?yàn)?Ibarrier 是非阻塞的拍霜,因此先到達(dá)的進(jìn)程并不會(huì)阻塞在此等待其它進(jìn)程,而是會(huì)立即返回一個(gè) MPI.Request 對(duì)象 req 并執(zhí)行后續(xù)的計(jì)算工作(此處進(jìn)入 while 循環(huán)輸出循環(huán)次數(shù))薪介,但是只要還有進(jìn)程沒有到達(dá)同步位置點(diǎn) Ibarrier祠饺,req.Test() 就會(huì)返回 False,只有當(dāng)所有進(jìn)程都執(zhí)行完 sleep 到達(dá) Ibarrier 位置汁政,req.Test() 的結(jié)果才會(huì)為 True道偷,所有進(jìn)程才會(huì)退出 while 循環(huán)缀旁。在非阻塞同步的過程中,運(yùn)行快的進(jìn)程(此處即 sleep 時(shí)間少的進(jìn)程)不會(huì)等待運(yùn)行慢的進(jìn)程试疙,而是會(huì)利用此時(shí)間做更多其它的計(jì)算任務(wù)(此處輸出更多的循環(huán)計(jì)數(shù))诵棵。可見非阻塞的柵障同步操作可以避免運(yùn)行快的進(jìn)程的不必要的等待時(shí)間以提高程序的計(jì)算效率祝旷。
以上我們介紹了 mpi4py 中的非阻塞集合通信方法履澳,在下一篇中我們將介紹 mpi4py 中的近鄰集合通信方法。