在上一篇中我們概要地介紹了最新的 MPI-3 標準中引進的新特性,mpi4py 3.0.0 支持 MPI-3 的很多新特性,我們將在後面逐步介紹,下面我們首先介紹 mpi4py 中的非阻塞集合通信。
在前面我們介紹了 mpi4py 中的集合通信,不過前面介紹的是 MPI-1 和 MPI-2 標準下的集合通信方法,所有這些方法都是阻塞式的,在通信沒有完成之前,這些方法不會返回,因此無法進行後面的計算任務。MPI-3 標準引進了所有這些集合通信方法的非阻塞版本,mpi4py 中的非阻塞集合通信方法與其對應的阻塞版本有着完全一樣的方法接口,不同的是這些非阻塞方法會返回一個 MPI.Request 對象,然後可以通過該對象的 Test 或者 Wait 等方法來測試或等待通信的完成,這部分與非阻塞的點到點通信的測試和等待是一樣的。非阻塞集合通信也可以防止死鎖,並通過將通信和計算重疊而提高程序的運行效率。
方法接口
下面給出非阻塞集合通信的方法接口。
MPI.Comm.Ibcast(self, buf, int root=0)
非阻塞廣播操作。對應阻塞版本的 MPI.Comm.Bcast,返回 MPI.Request 對象。
MPI.Comm.Iscatter(self, sendbuf, recvbuf, int root=0)
非阻塞發散操作。對應阻塞版本的 MPI.Comm.Scatter,返回 MPI.Request 對象。
MPI.Comm.Iscatterv(self, sendbuf, recvbuf, int root=0)
非阻塞向量發散操作。對應阻塞版本的 MPI.Comm.Scatterv,返回 MPI.Request 對象。
MPI.Comm.Igather(self, sendbuf, recvbuf, int root=0)
非阻塞收集操作。對應阻塞版本的 MPI.Comm.Gather,返回 MPI.Request 對象。
MPI.Comm.Igatherv(self, sendbuf, recvbuf, int root=0)
非阻塞向量收集操作。對應阻塞版本的 MPI.Comm.Gatherv,返回 MPI.Request 對象。
MPI.Comm.Ireduce(self, sendbuf, recvbuf, Op op=SUM, int root=0)
非阻塞規約操作。對應阻塞版本的 MPI.Comm.Reduce,返回 MPI.Request 對象。
MPI.Comm.Iallgather(self, sendbuf, recvbuf)
非阻塞全收集操作。對應阻塞版本的 MPI.Comm.Allgather,返回 MPI.Request 對象。
MPI.Comm.Iallgatherv(self, sendbuf, recvbuf)
非阻塞向量全收集操作。對應阻塞版本的 MPI.Comm.Allgatherv,返回 MPI.Request 對象。
MPI.Comm.Iallreduce(self, sendbuf, recvbuf, Op op=SUM)
非阻塞全規約操作。對應阻塞版本的 MPI.Comm.Allreduce,返回 MPI.Request 對象。
MPI.Comm.Ireduce_scatter_block(self, sendbuf, recvbuf, Op op=SUM)
非阻塞非向量規約發散操作。對應阻塞版本的 MPI.Comm.Reduce_scatter_block,返回 MPI.Request 對象。
MPI.Comm.Ireduce_scatter(self, sendbuf, recvbuf, recvcounts=None, Op op=SUM)
非阻塞向量規約發散操作。對應阻塞版本的 MPI.Comm.Reduce_scatter,返回 MPI.Request 對象。
MPI.Comm.Ialltoall(self, sendbuf, recvbuf)
非阻塞全發散操作。對應阻塞版本的 MPI.Comm.Alltoall,返回 MPI.Request 對象。
MPI.Comm.Ialltoallv(self, sendbuf, recvbuf)
非阻塞向量全發散操作。對應阻塞版本的 MPI.Comm.Alltoallv,返回 MPI.Request 對象。
MPI.Comm.Ialltoallw(self, sendbuf, recvbuf)
非阻塞向量全發散操作。對應阻塞版本的 MPI.Comm.Alltoallw,返回 MPI.Request 對象。
MPI.Intracomm.Iscan(self, sendbuf, recvbuf, Op op=SUM)
非阻塞掃描操作。對應阻塞版本的 MPI.Comm.Scan,返回 MPI.Request 對象。注意:此方法只在組內通信子上有定義。
MPI.Intracomm.Iexscan(self, sendbuf, recvbuf, Op op=SUM)
非阻塞前綴掃描操作。對應阻塞版本的 MPI.Comm.Exscan,返回 MPI.Request 對象。注意:此方法只在組內通信子上有定義。
MPI.Comm.Ibarrier(self)
非阻塞柵障同步操作。對應阻塞版本的 MPI.Comm.Barrier,返回 MPI.Request 對象。
例程
下面給出部分非阻塞集合操作的使用例程。
# 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)
運行結果如下:
$ 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 外都比較容易理解,因爲它們同其對應的阻塞版本有着同樣的語義。但是非阻塞的柵障同步操作 Ibarrier 卻讓人感到有點匪夷所思。因爲 Ibarrier 是非阻塞的,並不會讓運行快的進程停在此等待其它進程,表面上看似乎起不到讓進程同步的作用。實際上非阻塞的柵障同步 Ibarrier 是一個非常有用的操作,合適地使用它可以大大提高程序的計算效率。Ibarrier 雖然不會讓進程阻塞下來等待,但是到達 Ibarrier 的進程會宣告自己已經到達了需要同步的執行點,然後可以去做其它不依賴於此同步的工作,而不必像在阻塞同步中那樣在此白白等待,其可以週期性地通過 Test 等方法來檢測是否所有的進程都已經到達需要同步的點,只要還有進程沒有到達該點,Test 的結果就會爲 False,一旦所有進程都到達了同步點,Test 的結果就會爲 True,表示完成了所需的同步工作,所有進程可以進行後續依賴於此同步的計算任務。
打個簡單的比方,幾個人商量好 9 點在某會議室開會,只有等所有人都到齊後纔會舉行會議,有的人可能會早於 9 點到達,有的人可能會遲到,在阻塞同步的情況下,先到的人都會在會議室什麼事都不做白白等着最後一個到的人,而在非阻塞同步情況下,先到的人可以在會議室籤個到或留張小紙條以表明自己已經到了,然後可以到附近溜達溜達,比如說喝杯咖啡或做點其它與會議無關的事情,並每過一段時間回來看看簽到表或小紙條是否所有人都已到齊,只要還有人沒到,就可以接着做點自己的事情,一旦所有人都已到齊,會議就可以舉行了。
下面給出非阻塞柵障同步操作的簡單示例。
# 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
# ...
運行結果如下:
$ 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
在以上例程中,由於每個進程 sleep 的時間不同,因此到達 Ibarrier 的時間也不同,但是因爲 Ibarrier 是非阻塞的,因此先到達的進程並不會阻塞在此等待其它進程,而是會立即返回一個 MPI.Request 對象 req 並執行後續的計算工作(此處進入 while 循環輸出循環次數),但是隻要還有進程沒有到達同步位置點 Ibarrier,req.Test() 就會返回 False,只有當所有進程都執行完 sleep 到達 Ibarrier 位置,req.Test() 的結果纔會爲 True,所有進程纔會退出 while 循環。在非阻塞同步的過程中,運行快的進程(此處即 sleep 時間少的進程)不會等待運行慢的進程,而是會利用此時間做更多其它的計算任務(此處輸出更多的循環計數)。可見非阻塞的柵障同步操作可以避免運行快的進程的不必要的等待時間以提高程序的計算效率。
以上我們介紹了 mpi4py 中的非阻塞集合通信方法,在下一篇中我們將介紹 mpi4py 中的近鄰集合通信方法。