第三章 MPI編程
3.1 MPI簡單介紹
多線程是一種便捷的模型,當中每一個線程都能夠訪問其他線程的存儲空間。因此,這樣的模型僅僅能在共享存儲系統之間移植。一般來講,並行機不一定在各處理器之間共享存儲,當面向非共享存儲系統開發並行程序時,程序的各部分之間通過來回傳遞消息的方式通信。要使得消息傳遞方式可移植,就須要採用標准的消息傳遞庫。這就促成的消息傳遞接口(Message Passing Interface, MPI)的面世,MPI是一種被廣泛採用的消息傳遞標准[1]。
與OpenMP並行程序不同,MPI是一種基於消息傳遞的並行編程技術。消息傳遞接口是一種編程接口標准,而不是一種詳細的編程語言。簡而言之,MPI標准定義了一組具有可移植性的編程接口。各個廠商或組織遵循這些標准實現自己的MPI軟件包,典型的實現包含開放源碼的MPICH、LAM MPI以及不開放源碼的Intel MPI。因為MPI提供了統一的編程接口,程序猿僅僅須要設計好並行算法,使用對應的MPI庫就能夠實現基於消息傳遞的並行計算。MPI支持多種操作系統,包含大多數的類UNIX和Windows系統。
3.1.1怎樣實現MPI
MPI是一個標准。它不屬於不論什么一個廠商,不依賴於某個操作系統,也不是一種並行編程語言。不同的廠商和組織遵循着這個標准推出各自的實現,而不同的實現也會有其不同的特點。MPICH是影響最大、用戶最多的MPI實現。眼下可下載的最新的MPICH軟件包為MPICH1.2.7pl和2008年2月15日公布的MPICH 2-1.0.7測試版(我使用的是MPICH 2-1.0.6pl),在http://www.mcs.anl.gov/research/projects/mpich2/index.php能夠下載到,分別有支持UNIX和Windows的32位和64位版本號。
3.1.2 MPI程序的特點
MPI程序是基於消息傳遞的並行程序。消息傳遞指的是並行運行的各個進程具有自己獨立的堆棧和代碼段,作為互不相關的多個程序獨立運行,進程之間的信息交互全然通過顯示地調用通信函數來完畢。
3.2 MPICH的安裝和配置
我使用的MPICH2安裝文件是mpich2-1.0.6p1-win32-ia32.msi,在Windows下安裝MPICH2比較簡單,可是要有Microsoft .NET Framework 2.0的支持。安裝基本上僅僅要單擊“Next”就可以。在安裝過程中會提示輸入進程管理器的password,這個password被用來訪問全部的程序,這里使用的password為admin。
安裝完畢后,安裝文件夾下的include子文件夾包括了編程所須要的全部頭文件,lib子文件夾包括了對應的程序庫,而子文件夾bin則包括了MPI在Windows以下必須的執行程序。執行時須要的動態鏈接庫被安裝在了Windows系統文件夾中。在Windows平台下能夠使用Microsoft Visual Studio來開發MPI程序,以下舉例說明。
首先,新建一個Win32控制台項目,然后將MPICH2安裝文件夾下的include
圖3-1 配置頭文件文件夾
子文件夾添�到頭文件文件夾中。在VS 2005的菜單 工具->選項->項目解決方式->VC++文件夾對話框中增�include子文件夾,如圖3-1所看到的。再用同樣的方法將MPICH2\lib添�到庫文件文件夾中,如圖3-2。

圖3-2 配置庫文件文件夾
為了避免名字沖突,須要在預編譯頭文件stdafx.h中添�#inlcude mpi.h語句。如今就能夠在主程序文件里編寫MPI程序了,MPI的開發環境配置完成。
3.3 在Windows下怎樣執行MPI程序
我所進行的MPI程序的開發均是在Windows平台下,使用Visual Studio 2005 + MPIEXEC wrapper 進行的,首先用一個簡單的Hello World 程序說明執行環境的配置。
依照上一小節介紹配置好開發環境之后,在VS 2005中新建立一個Win32 控制台項目,並取名MPI1,在MPI1.CPP文件里輸入以下的程序。在項目屬性的“配置屬性”->“常規”項中的“字符集”設置為“未設置”,如圖3-3所看到的。
例3_1
int _tmain(int argc, _TCHAR* argv[])
{ int rank, size;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
printf("Hello World from thread %d of %d\n", rank, size);
MPI_Finalize();
return 0;
}
這個程序比較簡單,在函數MPI_Init()和MPI_Finalize()之間是程序並行運行的地方,MPI_Init()、MPI_Comm_rank()、MPI_Comm_size()和MPI_Finalize(),這四個函數是MPI中最重要和最經常使用的函數。以下分別說明:
圖3-3 配置項目屬性
(1) MPI_Init和MPI_Finalize
MPI_Init用來初始化MPI運行環境,建立多個MPI進程之間的聯系,為興許通信做准備。而MPI_Finalize則是結束MPI運行環境。這兩個函數就是定義MPI程序的並行區的,除了檢測是否初始化的函數之外,不應該在這兩個函數定義的區域外調用其他MPI函數。這兩個函數都返回整型值,標識函數是否調用成功。
(2) MPI_Comm_rank
MPI_Comm_rank函數就是用來標識各個MPI進程的,給出調用該函數的進程的進程號。MPI_Comm_rank返回整型的錯誤值,須要提供兩個參數:
l MPI_Comm類型的通信域,標識參與計算的MPI進程組。上面樣例中使用的是MPI_COMM_WORLD,這個進程組是MPI實現預先定義好的進程組,指的是全部MPI進程所在的進程組。假設想要申請自己的特殊的進程組,則須要通過MPI_Comm定義並通過其他MPI函數生成。
l &rank返回調用進程中的標識號。
MPI還定義了還有一個進程組MPI_COMM_SELF,僅僅包括各個進程自己的進程組。
(3) MPI_Comm_size
這個函數則用來標識對應進程組中有多少個進程,它也有兩個參數:
l MPI_Comm類型的通信域,標識參與計算的MPI進程組。上面的樣例中用的是MPI_COMM_WORLD。
l &size返回對應進程組中的進程數。
執行這個程序,執行結果如圖3-4,依照並行執行的方式,上面程序執行結果應該打印兩行文字信息,為:
Hello World from thread 0 of 2
Hello World from thread 1 of 2

圖 3-4 例3_1在windows上的執行結果
(本機系統環境變量OMP_NUM_THREADS值是2),可是執行結果確僅僅打印了一行,顯然函數MPI_Init和MPI_Finalize之間的代碼僅被一個線程串行執行了。經過查詢資料知道,MPI程序若要被正確執行須要使用MPICH2安裝文件夾下的執行工具MPIEXEC wrapper執行用VS 2005生成的exe文件。啟動這個程序,程序的界面如圖3-5

圖 3-5 MPIEXEC wrapper程序界面
因為該程序僅僅有操作系統的管理員才有權使用,所以在第一次執行時須要輸入計算機username和口令,而且不同意口令為空,如圖3-6。輸入完畢后,單擊“Register”button完畢注冊,之后就能夠使用該工具執行MPI程序了。
在“Application”欄中選擇要執行的exe程序,在“Number of process”欄中選擇要執行程序的線程數,然后單擊“Execute”button執行程序。如用4線程執行上面的演示樣例程序,輸出結果如圖3-7所看到的。

圖 3-6 輸入系統username和口令

圖 3-7 使用MPIEXEC wrapper執行例3_1的結果
4線程分別運行MPI_Init和MPI_Finalize之間的代碼,打印4行信息,程序運行結果正確。
3.4 MPI的點對點通信
點對點通信是MPI程序的基礎,MPI_Send和MPI_Recv是兩個最重要的函數。這兩個函數的標准形式是:
l int MPI_Send(buf, counter, datatype, dest, tag, comm)
參數作用例如以下:
buf:發送緩沖區的起始地址,能夠是數組或結構指針
count:非負整數,發送的數據個數
datatype:發送數據的數據類型
dest:整型,目的的進程號
tag:整型,消息標志
comm:MPI進程組所在的通信域
這個函數返回整型的錯誤碼,它的含義是向通信域中的dest進程發送數據,數據存放在buf中,類型是datatype,個數是count,這個消息的標志是tag,用以和本進程向同一目的進程發送的其他消息差別開來。
l int MPI_Recv(buf, count, datatype, source, tag, comm, status)
參數作用例如以下:
buf:接收緩沖區的起始地址,能夠是數組或結構指針
count:非負整數,最多可接收的數據個數
datatype:接收數據的數據類型
source:整型,接收數據的來源,即發送數據進程的進程號
tag:整型,消息標識,應與發送操作的消息標識同樣
comm:消息接收進程所在的通信域
status:MPI_Status結構指針,返回狀態信息
這個函數返回整型的錯誤碼,它的含義是進程從comm域中source進程接收標簽號為tag的數據,並保存到buf中。接收緩沖區buf的大小不能小於發送過來的消息的長度。否則會因為數組越界導致程序出錯。參數status是MPI_Status類型的,status主要顯示接收函數的各種錯誤狀態。通過訪問status.MPI_SOURCE、status.MPI_TAG和status.MPI_ERROR就能夠得到發送數據的進程號、使用的標簽以及接收操作的錯誤代碼。另外,還能夠使用函數MPI_Get_count來獲得實際接收到的數據項數。MPI_Get_count的標准定義為:int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count);將實際接收到數據項數存放到count中。以下用一個程序說明上面提到的函數的用法。
演示樣例程序見例3_2
程序的執行結果如圖3-8(4個進程)
函數MPI_Get_processor_name用於獲得計算機名,並存放在processor_name中,長度為namelen,宏定義MPI_MAX_PROCESSOR_NAME是機器名的最大長度。這個程序的完畢的任務是使進程i發送數據給進程i+1,並等待由進程i-1發送來的數據。最后一個進程則發送數據給進程0。
3.5統計時間函數
為了驗證程序並行化后的效果,MPI提供了兩個用於統計時間的函數 MPI_Wtime和MPI_Wtick。當中MPI_Wtime返回一個雙精度數,表示從過去某點的時刻到當前時刻所消耗的時間秒數。而函數MPI_Wtick則返回MPI_Wtime結果的精度。改動例3_2程序,在並行代碼兩端添�統計時間的函數,如例3_3:
例 3_3(完整程序見演示樣例程序4_3)
begin = MPI_Wtime();
end = MPI_Wtime();
diff = end - begin;
printf("%d process time is %9.7f\n", myid, diff);
printf("%d process tick is %9.7f\n", myid, MPI_Wtick());
}
執行結果如圖3-9:

圖 3-8 例3_2的執行結果

圖 3-9 例3_3的執行結果
3.6負載均衡對程序性能的影響
在並行計算中,假設各個處理器上的工作所須要的完畢時間不同,則會使先完畢工作的處理器等待未完畢工作的處理器,浪費了計算資源。這時應該使各個處理器的負載盡量均衡。一般採用的策略有兩種:靜態負載平衡和動態負載平衡。前者適用於計算前能夠准確知道負載,並且這些負載easy平均划分給各個進程的情況。而對於事先不知道負載情況,或者總負載不易划分的情況,則須要採用動態負載划分來解決。在動態負載平衡模式中存在一個管理結點負責給各個進程分配任務,當一個進程完畢當前的計算任務后,它就向管理結點申請新的任務,假設還有未分配的任務,管理結點就將任務分配給那個進程,這有點類似於計算機硬件向CPU發中斷請求服務的方式。
3.7 開發實例
以下將在Windows平台上使用MPI編寫一個用數值積分法計算圓周率的程序。利用公式PI=
的近似值計算圓周率[7],定積分的計算能夠轉化為求一個曲邊梯形的面積問題。將積分區間等分成n個小的子區間,可將每一個小的子區間上的曲邊梯形近似地看成矩形,這些矩形面積的和就近似地等於原來曲邊梯形的面積。這樣終於將求圓周率的問題轉化成了一個面積迭加的計算。每一個小矩形的寬為

(n為將積分區間等分的份數),高能夠將x值帶入函數

求得。用循環將每一個小矩形的面積累加起來便是PI的近似值。詳細的算法實現見附加中的程序“mpi_pi”。圖3-10、3-11各自是用一個進程和兩個進程執行的結果。

圖3-10 使用一個進程的執行結果

圖3-11 使用兩個進程的執行結果
從執行結果能夠看到使用兩個進程時的計算速度反而不如用一個進程執行時的速度,這時因為本程序的計算規模不大,另外引入一個進程的開銷大於程序並行所帶來的益處,所以進程數越多反而程序的執行速度越慢。看以下一組數據[8](表3-1)
計算機數 |
計算時間 |
1 |
1.63643 |
2 |
0.83180 |
3 |
0.55622 |
這組數據是在不同的硬件平台下實現本開發實例程序的計算時間。執行環境為3 台計算機組成的集群, 配置均為CPU : Intel PentiumIII 733MHz,同樣的算法,隨着參與計算的機器數添加�,計算時間降低。
MPI是針對分布式計算機系統提出的,它採用非共
表3-1 享內存的方式利用多進程完畢並行任務,當計算規模不大或處理器數量不多時,很多其它進程的維護會添加�系統的開銷,並且進程之間的通信存在延時。它比較適合集群計算機系統。
3.8 小結
本章對MPI編程進行了初步研究,介紹了MPI程序的特點、軟件包的安裝、MPI程序的執行方式。
MPI是一種基於消息傳遞的並行編程技術,而不是一種詳細的編程語言。MPI程序與OpenMP程序的最大不同就是MPI程序不僅能夠適用多線程的方式並行運算還能夠讓程序以多進程的方式運行,以這樣的方式運行的程序並不共享內存,各個進程是通過消息傳遞來進行通信的。這樣做的優點是完畢某一計算任務的不同進程能夠運行在不同處理器上(不僅僅是處理器的不同核上),甚至是不同的結點計算機上,方便分布式計算系統的構建。在多核上使用MPI能夠採用兩種方式,一種是在多核平台上開發傳統的多進程MPI並行程序,一個核運行一個MPI進程。第二種方式是採用MPI + OpenMP的方法,在結點內採用多線程方式,結點間採用MPI多進程方式。
轉自:http://blog.csdn.net/gexplore/article/details/7078832