python doc2 —— MPI多線程並行計算工具mpi4py

1. MPI

MPI的全稱是Message Passing Interface,即消息傳遞接口。

  • 它並不是一門語言,而是一個庫,我們可以用Fortran、C、C++結合MPI提供的接口來將串行的程序進行並行化處理,也可以認爲Fortran+MPI或者C+MPI是一種再原來串行語言的基礎上擴展出來的並行語言。
  • 它是一種標準而不是特定的實現,具體的可以有很多不同的實現,例如MPICH、OpenMPI等。
  • 它是一種消息傳遞編程模型,顧名思義,它就是專門服務於進程間通信的。

MPI的工作方式很好理解,我們可以同時啓動一組進程,在同一個通信域中不同的進程都有不同的編號,程序員可以利用MPI提供的接口來給不同編號的進程分配不同的任務和幫助進程相互交流最終完成同一個任務。就好比包工頭給工人們編上了工號然後指定一個方案來給不同編號的工人分配任務並讓工人相互溝通完成任務。

2. 基本MPI函數

mpi4py是一個構建在MPI之上的Python庫,主要使用Cython編寫。mpi4py使得Python的數據結構可以方便的在多進程中傳遞。

mpi4py是一個很強大的庫,它實現了很多MPI標準中的接口,包括點對點通信,組內集合通信、非阻塞通信、重複非阻塞通信、組間通信等,基本上我能想到用到的MPI接口mpi4py中都有相應的實現。不僅是Python對象,mpi4py對numpy也有很好的支持並且傳遞效率很高。同時它還提供了SWIG和F2PY的接口能夠讓我們將自己的Fortran或者C/C++程序在封裝成Python後仍然能夠使用mpi4py的對象和接口來進行並行處理。可見mpi4py的作者的功力的確是非常了得。

2.1 工具

a. 通信子(通信空間)

MPI_COMM_WORLD在MPI中的作用:

  • 一個通信空間是一個進程組和一個上下文的組合.上下文可看作爲組的超級標籤,用於區分不同的通信子.
  • 在執行函數MPI_Init之後,一個MPI程序的所有進程形成一個缺省的組,這個組的通信子即被寫作MPI_COMM_WORLD.
  • 該參數是MPI通信操作函數中必不可少的參數,用於限定參加通信的進程的範圍.
from mpi4py import MPI
                                                
MPI.COMM_SELF
# <mpi4py.MPI.Intracomm at 0x7f2fa2fd59d0>
                                                
MPI.COMM_WORLD
# <mpi4py.MPI.Intracomm at 0x7f2fa2fd59f0>

b. 獲取進程

轉到python中的mpi4py

from mpi4py import MPI
# 用Get_size 獲得進程個數 p
size = MPI.COMM_WORLD.Get_size()
# Get_rank 獲得進程的一個叫rank的值,
# 該rank值爲0到p-1間的整數,相當於進程的ID號
rank = MPI.COMM_WORLD.Get_rank()

c++中的定義類似

int MPI_Comm_size(MPI_Comm comm, int *size)
int MPI_Comm_rank(MPI_Comm comm, int *rank)

3. 通信

因爲mpi4py中點對點的 通信send語句,在數據量較小的時候是把發送數據拷貝到緩存區,是非堵塞的操作, 然而在數據量較大時候是堵塞操作。
阻塞操作見 python的MPI多線程並行通信方式

3.1 點對點通信

所謂點對點通信,即單個線程與單個線程之間通信。

(a) 兩個線程之間點對點通信。

# test.py
from mpi4py import MPI

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

if rank == 0:
    data = {'a': 7, 'b': 3.14}
    comm.send(data, dest=1, tag=11)
    data['a'] = 8; data['b'] = data['b'] * 2

elif rank == 1:
    data = comm.recv(source=0, tag=11)
print('{}_{}'.format(rank, data))

結果爲:

mpiexec -n 2 python test.py     # -n/-np  means number of process
# result
0_{'a': 8, 'b': 6.28}
1_{'a': 7, 'b': 3.14}

(b) 多個線程之間點對點通信

如下面例子的5個線程之間點對點通信,一個接着一個通信之間通信,形成一個圈(rank_3 => rank_4 => rank_0 => rank_1 => rank_2 => rank_3)。

# test.py
import mpi4py.MPI as MPI
 
comm = MPI.COMM_WORLD
comm_rank = comm.Get_rank()      # id number
comm_size = comm.Get_size()      # number of mpi

# point to point communication
data_send = [comm_rank+1]*5

comm.send(data_send,dest=(comm_rank+1)%comm_size)
data_recv =comm.recv(source=(comm_rank-1)%comm_size)

print("my rank is %d, and I received:" % comm_rank)
print(data_recv)

運行該文件:

mpiexec -n 5 python test.py
# result
my rank is 3, and I received:
[3, 3, 3, 3, 3]
my rank is 0, and I received:
[5, 5, 5, 5, 5]
my rank is 4, and I received:
[4, 4, 4, 4, 4]
my rank is 1, and I received:
[1, 1, 1, 1, 1]
my rank is 2, and I received:
[2, 2, 2, 2, 2]

解釋:(rank_3 => rank_4 => rank_0 => rank_1 => rank_2 => rank_3)
例如,當前進程id=3,其data_send = [comm_rank+1]*5爲[4, 4, 4, 4, 4], 而該進程發送時,所設定的dest=(comm_rank+1)%comm_size)=4,接收設定爲source=3,因此rank=3原本的數據[4, 4, 4, 4, 4],發送到目標dest=4,所以rank=4所接收到的數據爲[4, 4, 4, 4, 4],而rank=3自己接收的來源source=3,故dest計算等於3的爲rank=2,所以id=3獲取其數據[3, 3, 3, 3, 3],並打印出來。

如果修改到如下情況,則多線程之間可以按照順序點對點的通信,即(rank_1 => rank_2 => rank_3 => rank_4 => rank_0 => rank_1)

import mpi4py.MPI as MPI
     
comm = MPI.COMM_WORLD
comm_rank = comm.Get_rank()
comm_size = comm.Get_size()
data_send = [comm_rank+1]*5

if comm_rank == 0:
   comm.send(data_send, dest=(comm_rank+1)%comm_size)

if comm_rank > 0:
   data_recv = comm.recv(source=(comm_rank-1)%comm_size)
   comm.send(data_send, dest=(comm_rank+1)%comm_size)

if comm_rank == 0:
   data_recv = comm.recv(source=(comm_rank-1)%comm_size)

print("my rank is %d, and Ireceived:" % comm_rank)
print(data_recv)

3.2 羣體通信

a. 廣播 bcast

廣播操作是典型的一對多通信,將跟進程的數據複製到同組內其他所有進程中。
在這裏插入圖片描述

from mpi4py import MPI                                                     
                                                                           
comm = MPI.COMM_WORLD                                                      
rank = comm.Get_rank()                                                     
size = comm.Get_size()                                                     
                                                                           
if rank == 0:                                                              
    data = list(range(10))                                                       
    print("process {} bcast data {} to other processes".format(rank, data))
else:                                                                      
    data = None                                                            
data = comm.bcast(data, root=0)                                            
print("process {} recv data {}...".format(rank, data))
mpiexec -n 5 python test.py
# result
process 0 bcast data [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] to other processes
process 0 recv data [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]...
process 1 recv data [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]...
process 4 recv data [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]...
process 2 recv data [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]...
process 3 recv data [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]...

b. 發散 scatter

與廣播不同,發散可以向不同的進程發送不同的數據,而不是完全複製。
在這裏插入圖片描述

from mpi4py import MPI                                                            
import numpy as np                                                                
                                                                                  
comm = MPI.COMM_WORLD                                                             
rank = comm.Get_rank()                                                            
size = comm.Get_size()                                                            
                                                                                  
recv_data = None                                                                  
                                                                                  
if rank == 0:                                                                     
    send_data = list(range(5))                                                         
    print("process {} scatter data {} to other processes".format(rank, send_data))
else:                                                                             
    send_data = None                                                              
recv_data = comm.scatter(send_data, root=0)                                       
print("process {} recv data {}...".format(rank, recv_data))
mpiexec -n 5 python test.py

c. 收集 gather

收集過程是發散過程的逆過程,每個進程將發送緩衝區的消息發送給根進程,根進程根據發送進程的進程號將各自的消息存放到自己的消息緩衝區中。
在這裏插入圖片描述

from mpi4py import MPI                                              
import numpy as np                                                  
                                                                    
comm = MPI.COMM_WORLD                                               
rank = comm.Get_rank()                                              
size = comm.Get_size()                                              
                                                                    
send_data = rank                                                    
print "process {} send data {} to root...".format(rank, send_data)  
recv_data = comm.gather(send_data, root=0)                          
if rank == 0:                                                       
    print "process {} gather all data {}...".format(rank, recv_data)
mpiexec -n 5 python test.py

d. 規約 reduce


參考資料:
[1] https://mpi4py.readthedocs.io/en/latest/tutorial.html
[2] https://mpitutorial.com/tutorials/mpi-introduction/
[3] https://zhuanlan.zhihu.com/p/25332041
[4] https://www.cnblogs.com/devilmaycry812839668/p/9484644.html
[5] https://blog.csdn.net/ZuoShifan/article/details/80024380
[6] https://www.zybuluo.com/Purpose/note/700033
[7] https://rabernat.github.io/research_computing/parallel-programming-with-mpi-for-python.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章