前言
計算機編程語言很多颖低,但是適合高性能數(shù)值計算的語言卻并不多,在高性能計算的項目中通常會使用到的語言有 Fortran陌宿、C锡足、C++ 等,他們是傳統(tǒng)的高性能計算機語言壳坪,這主要得益于它們的靜態(tài)編譯特性舶得,使得有它們生成的機器代碼,在底層上做了很多優(yōu)化爽蝴,能夠充分發(fā)揮硬件的性能沐批,但是這一特性也限制了它們的靈活性和易用性。一些動態(tài)的計算機語言在靈活性和易用性方面有著明顯的優(yōu)勢蝎亚,但是由于性能等其他方面的原因卻并不適合用來做大規(guī)模的數(shù)值計算珠插,更別說用到高性能計算領(lǐng)域了。這其中有一個例外颖对,就是 Python 計算機語言捻撑。作為一種解釋型的動態(tài)語言,并且不需要類型聲明缤底,Python 非常的簡單易用顾患,但是其運行性能卻比 Fortran、C个唧、C++ 等常規(guī)的高性能數(shù)值計算語言要差的多江解。但是近年來 Python 卻在數(shù)值計算領(lǐng)域占據(jù)了越來越大的份額,甚至在高性能計算領(lǐng)域也看到越來越多 Python 的身影徙歼。這是因為 Python 已經(jīng)不僅僅只是作為一個單獨的計算機編程語言犁河,而是變成了一個由龐大的庫和工具組成的完整生態(tài)系統(tǒng),在數(shù)值計算方面也是如此魄梯,包含著有 Numpy蹂午、Scipy恕汇、Pandas 等構(gòu)成的科學(xué)數(shù)值計算棧。這些數(shù)值計算庫和工具在底層一般封裝和調(diào)用優(yōu)秀的由 Fortran、C瓷患、C++ 等實現(xiàn)的高效算法庫沐悦,因此在一定程度上彌補了 Python 本身性能上的不足涎显,卻絲毫不損害其靈活性和易用性摧茴。但是將 Python 應(yīng)用到高性能計算上還需要有相應(yīng)的支持工具,MPI(消息傳遞接口)就是其中非常重要的一個稀蟋。本文就將簡要介紹 mpi4py煌张,一個構(gòu)建在 MPI 之上的 Python庫,允許在 Python 環(huán)境下使用 MPI 接口進(jìn)行多進(jìn)程并行甚至分布式的高性能計算退客。
MPI (Message Passing Interface)
MPI 的全稱是 Message Passing Interface骏融,即消息傳遞接口。它是一種用于編寫并行程序的標(biāo)準(zhǔn),包括協(xié)議和和語義說明绎谦,他們指明其如何在各種實現(xiàn)中發(fā)揮其特性管闷,有 MPICH、OpenMPI 等一些具體的實現(xiàn)窃肠,提供 Fortran包个、C、C++ 的相應(yīng)編程接口冤留。MPI 的目標(biāo)是高性能碧囊,大規(guī)模性,和可移植性纤怒。MPI 在今天仍為高性能計算的主要模型糯而。
MPI 的工作方式很好理解,我們可以同時啟動一組進(jìn)程泊窘,在同一個通信域中不同的進(jìn)程都有不同的編號熄驼,程序員可以利用 MPI 提供的接口來給不同編號的進(jìn)程分配不同的任務(wù)和幫助進(jìn)程相互交流最終完成同一個任務(wù)。就好比包工頭給工人們編上了工號然后指定一個方案來給不同編號的工人分配任務(wù)并讓工人相互溝通完成任務(wù)烘豹。
MPI 的具體實現(xiàn)并沒有提供 Python 的編程接口瓜贾,這就使得我們沒法直接地使用 Python 調(diào)用 MPI 實現(xiàn)高性能的計算,不過幸運的是携悯,我們有 mpi4py祭芦。mpi4py 是一個構(gòu)建在 MPI 之上的 Python 庫,主要使用 Cython 編寫憔鬼,它以一種面向?qū)ο蟮姆绞教峁┝嗽?Python 環(huán)境下調(diào)用 MPI 標(biāo)準(zhǔn)的編程接口龟劲,這些接口是構(gòu)建在 MPI-2 C++ 編程接口的基礎(chǔ)之上的,因此和 C++ 的 MPI 編程接口非常類似轴或,了解和有 C昌跌、C++ MPI 編程經(jīng)驗的人很容易地上手和使用 mpi4py 編寫基于 MPI 的高性能并行計算程序。
mpi4py 簡介
mpi4py 是一個構(gòu)建在 MPI 之上的 Python 庫侮叮,它使得 Python 的數(shù)據(jù)結(jié)構(gòu)可以方便的在多進(jìn)程中傳遞避矢。
mpi4py 是一個很強大的庫悼瘾,它實現(xiàn)了很多 MPI 標(biāo)準(zhǔn)中的接口囊榜,包括點對點通信,集合通信亥宿、阻塞/非阻塞通信卸勺、組間通信等,基本上能用到的 MPI 接口都有相應(yīng)的實現(xiàn)烫扼。不僅是任何可以被 pickle 的 Python 對象曙求,mpi4py 對具有單段緩沖區(qū)接口的 Python 對象如 numpy 數(shù)組及內(nèi)置的 bytes/string/array 等也有很好的支持并且傳遞效率很高。同時它還提供了 SWIG 和 F2PY 的接口能夠?qū)?C/C++ 或者 Fortran 程序在封裝成 Python 后仍然能夠使用 mpi4py 的對象和接口來進(jìn)行并行處理。
下面對在 Python 環(huán)境中使用 mpi4py 的接口進(jìn)行并行編程作一個簡要的介紹悟狱。
Python 對象及數(shù)組的傳遞
mpi4py 可以在不同的進(jìn)程間傳遞任何可以被 pickle 系列化的內(nèi)置和用戶自定義 Python 對象静浴,這些對象一般在發(fā)送階段被 pickle 系列化為 ASCII 或二進(jìn)制格式,然后在接收階段恢復(fù)成對應(yīng)的 Python 對象挤渐。
這種數(shù)據(jù)傳遞方式雖然簡單通用苹享,卻并不高效,特別是在傳遞大量的數(shù)據(jù)時浴麻。對類似于數(shù)組這樣的數(shù)據(jù)得问,準(zhǔn)確來說是具有單段緩沖區(qū)接口(single-segment buffer interface)的 Python 對象,如 numpy 數(shù)組及內(nèi)置的 bytes/string/array 等软免,可以用一種更為高效的方式直接進(jìn)行傳遞宫纬,而不需要經(jīng)過 pickle 系列化和恢復(fù)。
按照 mpi4py 的慣例膏萧,傳遞可以被 pickle 系列化的通用 Python 對象漓骚,可以使用通信子(Comm 類,后面會介紹)對象的以小寫字母開頭的方法榛泛,如 send()认境,recv(),bcast()挟鸠,scatter()叉信,gather() 等。但是如果要以更高效的方式傳遞具有單段緩沖區(qū)接口的 Python 對象艘希,如 numpy 數(shù)組硼身,則只能使用通信子對象的以大寫字母開頭的方法,如 Send()覆享,Recv()佳遂,Bcast(),Scatter()撒顿,Gather() 等丑罪。
MPI 環(huán)境管理
mpi4py 提供了相應(yīng)的接口 MPI.Init(),MPI.Init_thread() 和 MPI.Finalize() 來初始化和結(jié)束 MPI 環(huán)境凤壁。但是 mpi4py 通過在 init.py 中寫入了初始化的操作吩屹,因此在我們 from mpi4py import MPI 的時候就已經(jīng)自動初始化了 MPI 環(huán)境。
MPI_Finalize() 被注冊到了 Python 的 C 接口 Py_AtExit()拧抖,這樣在 Python 進(jìn)程結(jié)束時候就會自動調(diào)用 MPI_Finalize()煤搜, 因此不再需要我們顯式的去調(diào)用。
通信子(Communicator)
mpi4py 提供了相應(yīng)的通信子的 Python 類唧席,其中 MPI.Comm 是通信子的基類擦盾,在它下面繼承了 MPI.Intracomm 和 MPI.Intercomm 兩個子類嘲驾,這跟 MPI 的 C++ 實現(xiàn)中是相同的。下圖是通信子類的繼承關(guān)系迹卢。
同時它也提供了兩個預(yù)定義的通信子對象:
包含所有進(jìn)程的 MPI.COMM_WORLD辽故;
只包含調(diào)用進(jìn)程本身的 MPI.COMM_SELF。
可以由它們創(chuàng)建其它新的通信子腐碱。
可以通過通信子所定義的一些方法獲取當(dāng)前進(jìn)程號榕暇、獲取通信域內(nèi)的進(jìn)程數(shù)、獲取進(jìn)程組喻杈、對進(jìn)程組進(jìn)行集合運算彤枢、分割合并等等。
關(guān)于通信子與進(jìn)程組的操作在后面會進(jìn)行詳細(xì)的介紹筒饰。
點到點通信
點到點通信是消息傳遞系統(tǒng)最基本的功能缴啡,mpi4py 的點到點通信使得數(shù)據(jù)可以在一對進(jìn)程之間互相傳遞,一端發(fā)送瓷们,一端接收业栅。
mpi4py 中提供了一系列發(fā)送和接收函數(shù)用以在進(jìn)程間傳遞帶有 tag 的類型數(shù)據(jù)。數(shù)據(jù)的類型信息允許傳遞的數(shù)據(jù)在不同的計算機架構(gòu)下進(jìn)行相應(yīng)的轉(zhuǎn)換谬晕,同時也使得不經(jīng)過 pickle 而高效地傳遞不連續(xù)的數(shù)據(jù)和用戶自定義的數(shù)據(jù)變得可能碘裕。tag 使得接收端可以有選擇性地進(jìn)行消息的接收。
mpi4py 中的點到點通信包括阻塞攒钳、非阻塞和持續(xù)通信帮孔。下面分別對其做簡要的介紹。
阻塞通信
mpi4py 中最基本的發(fā)送和接收函數(shù)是阻塞式的不撑,這些函數(shù)會阻塞程序的執(zhí)行直到參與通信的數(shù)據(jù)緩沖區(qū)可以安全地被其它程序重用文兢。MPI.Comm.Send(), MPI.Comm.Recv()焕檬,MPI.Comm.Sendrecv() 及其對應(yīng)的 MPI.Comm.send()姆坚,MPI.Comm.recv(),MPI.Comm.sendrecv() 提供對阻塞通信的支持实愚。
非阻塞通信
所有的阻塞通信函數(shù)都提供了一個對應(yīng)的非阻塞的版本兼呵,這些非阻塞通信函數(shù)允許計算和通信的同時執(zhí)行,這在一些情況下可以極大地提高計算性能腊敲。mpi4py 中的 MPI.Comm.Isend() 和 MPI.Comm.Irecv() 會分別初始化一個發(fā)送和接收操作击喂,然后立即返回一個 MPI.Request 實例,在程序某個合適的地方可以調(diào)用 MPI.Request.Test()兔仰,MPI.Request.Wait() 和MPI.Request.Cancel() 來測試茫负、等待或者取消本次通信。
下面看看阻塞通信與非阻塞通信的對比:
非阻塞通信的消息發(fā)送和接受:
持續(xù)通信
在某些情況下可能需要持續(xù)不斷地發(fā)送和接收一些數(shù)據(jù)乎赴,這時可以用持續(xù)通信的方法來提高通信效率忍法。持續(xù)通信是一種特殊的非阻塞通信,它能夠有效地減小進(jìn)程和通信控制之間的相應(yīng)開銷榕吼。在 mpi4py 中可以用 MPI.Comm.Send_init() 和 MPI.Comm.Recv_init() 來創(chuàng)建一個持續(xù)發(fā)送和接收的操作請求饿序,它們會返回一個 MPI.Prequest 類的實例,然后可以用 MPI.Prequest.Start() 方法發(fā)起實際的通信操作羹蚣。
集合通信
集合通信允許在一個通信組內(nèi)的多個進(jìn)程之間同時傳遞數(shù)據(jù)原探,集合通信的語法和語意與點到點通信是一致的,不過集合通信只有阻塞版本顽素。
經(jīng)常用到的集合通信操作有以下這些:
障同步(Barrier synchronization)操作咽弦;
全局通信,包括廣播(Broadcast)胁出,收集(Gather)和發(fā)散(Scatter)型型;
全局規(guī)約,包括求和全蝶,最大闹蒜,最小等。
MPI 組通信和點到點通信的一個重要區(qū)別就是抑淫,在某個進(jìn)程組內(nèi)所有的進(jìn)程同時參加通信绷落,mpi4py 提供了方便的接口讓我們完成 Python 中的組內(nèi)集合通信,方便編程同時提高程序的可讀性和可移植性始苇。
下面簡要地介紹幾個集合通信的操作原理:
廣播(Broadcast)
廣播操作是典型的一對多通信砌烁,將根進(jìn)程的數(shù)據(jù)復(fù)制到同組內(nèi)其他所有進(jìn)程中。
發(fā)散(Scatter)
與廣播不同催式,發(fā)散可以向不同的進(jìn)程發(fā)送不同的數(shù)據(jù)往弓,而不是完全復(fù)制。
收集(Gather)
收集過程是發(fā)散過程的逆過程蓄氧,每個進(jìn)程將發(fā)送緩沖區(qū)的消息發(fā)送給根進(jìn)程函似,根進(jìn)程根據(jù)發(fā)送進(jìn)程的進(jìn)程號將各自的消息存放到自己的消息緩沖區(qū)中。
動態(tài)進(jìn)程管理
在 MPI-1 規(guī)范中喉童,一個 MPI 應(yīng)用程序是靜態(tài)的撇寞。一旦一個 MPI 程序運行起來后,就既不能增加也不能刪除進(jìn)程堂氯。MPI-2 規(guī)范沒有了這些限制蔑担,它增加了一個進(jìn)程管理模型,可以實現(xiàn)動態(tài)的進(jìn)程管理咽白,使得我們可以在 MPI 程序運行過程中創(chuàng)建新的進(jìn)程或者與其它運行中的 MPI 程序建立通信連接啤握。
在 mpi4py 中,可以調(diào)用 MPI.Intracomm.Spawn() 方法在一個組內(nèi)通信子內(nèi)創(chuàng)建一個新的獨立的通信組晶框,它會返回一個組間通信子(一個 MPI.Intercomm 類實例)排抬。這個新創(chuàng)建的通信子(稱作子通信子)可以通過調(diào)用 MPI.Comm.Get_parent() 來獲得創(chuàng)建它的通信子懂从,并與其進(jìn)行組間的點到點或集合通信。
單邊通信
單邊通信蹲蒲,也稱作遠(yuǎn)程內(nèi)存訪問(Remote Memory Access, RMA)番甩,這是對傳統(tǒng)的雙邊,基于發(fā)送/接收(send/receive)的 MPI 通信模型的一個補充届搁,單邊通信是一種基于單邊的推送/獲仍笛Α(put/get)MPI 通信模型。
在 mpi4py 中卡睦,可以通過 MPI.Win 類的實例實現(xiàn)單邊通信宴胧。在一個通信子內(nèi)調(diào)用MPI.Win.Create() 就可以創(chuàng)建一個 window 對象,這個 window 對象中包含著一個進(jìn)程可被用于遠(yuǎn)程讀寫的內(nèi)存緩沖區(qū)表锻。調(diào)用 MPI.Win.Free() 可以釋放掉一個不再需要的 window 對象恕齐。MPI.Win.Put(), MPI.Win.Get() 和 MPI.Win.Accumulate() 可以分別實現(xiàn)單邊的遠(yuǎn)程讀浩嫌、寫和規(guī)約操作檐迟。
并行 I/O
MPI 支持并行 I/O 操作。在 mpi4py 中所有的 MPI 并行操作都是通過 MPI.File 類實例完成的码耐。所有 MPI-2 中定義的30個用作并行 I/O 操作的方法在 mpi4py 中都有對應(yīng)的實現(xiàn)追迟。
總結(jié)
本文簡單介紹了可以使我們在 Python 環(huán)境下進(jìn)行多進(jìn)程的并行編程的軟件庫 mpi4py,MPI 的接口非常龐大骚腥,相應(yīng)的 mpi4py 也非常龐大敦间,給我們提供了非常強大而靈活的并行編程能力。mpi4py 還有實現(xiàn)了相應(yīng)的 SWIG 和 F2PY 的封裝文件和類型映射束铭,能夠幫助我們將 Python 同真正的 C/C++ 以及 Fortran 程序在消息傳遞上實現(xiàn)統(tǒng)一廓块。
在下一篇中我們將介紹如何安裝和使用 mpi4py。