MPI編程入門詳解
- 2020 年 2 月 14 日
- 筆記
MPI簡介
說到並行計算,我們有一個不可繞開的話題——MPI編程。MPI是一個跨語言的通訊協議,用於編寫並行電腦。支援點對點和廣播。MPI是一個資訊傳遞應用程式介面,包括協議和和語義說明,他們指明其如何在各種實現中發揮其特性。MPI的目標是高性能,大規模性,和可移植性。MPI在今天仍為高性能計算的主要模型。與OpenMP並行程式不同,MPI是一種基於資訊傳遞的並行編程技術。消息傳遞介面是一種編程介面標準,而不是一種具體的程式語言。簡而言之,MPI標準定義了一組具有可移植性的編程介面。
MPI基本函數
MPI調用借口的總數雖然龐大, 但根據實際編寫MPI的經驗, 常用的MPI調用的個數確什麼有限。 下面是6個最基本的MPI函數。 1. MPI_Init(…); 2. MPI_Comm_size(…); 3. MPI_Comm_rank(…); 4. MPI_Send(…); 5. MPI_Recv(…); 6. MPI_Finalize(); 我們在此通過一個簡單的例子來說明這6個MPI函數的基本用處。
函數介紹
1. int MPI_Init (int* argc ,char** argv[] )
該函數通常應該是第一個被調用的MPI函數用於並行環境初始化,其後面的程式碼到 MPI_Finalize()函數之前的程式碼在每個進程中都會被執行一次。 – 除MPI_Initialized()外, 其餘所有的MPI函數應該在其後被調用。 – MPI系統將通過argc,argv得到命令行參數(也就是說main函數必須帶參數,否則會出錯)。
2. int MPI_Finalize (void)
– 退出MPI系統, 所有進程正常退出都必須調用。 表明並行程式碼的結束,結束除主進程外其它進程。 – 串列程式碼仍可在主進程(rank = 0)上運行, 但不能再有MPI函數(包括MPI_Init())。
3. int MPI_Comm_size (MPI_Comm comm ,int* size )
– 獲得進程個數 size。 – 指定一個通訊子,也指定了一組共享該空間的進程, 這些進程組成該通訊子的group(組)。 – 獲得通訊子comm中規定的group包含的進程的數量。
4. int MPI_Comm_rank (MPI_Comm comm ,int* rank)
– 得到本進程在通訊空間中的rank值,即在組中的邏輯編號(該 rank值為0到p-1間的整數,相當於進程的ID。)
5. int MPI_Send( void *buff, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
–void *buff:你要發送的變數。 –int count:你發送的消息的個數(注意:不是長度,例如你要發送一個int整數,這裡就填寫1,如要是發送「hello」字元串,這裡就填寫6(C語言中字元串未有一個結束符,需要多一位))。 –MPI_Datatype datatype:你要發送的數據類型,這裡需要用MPI定義的數據類型,可在網上找到,在此不再羅列。 –int dest:目的地進程號,你要發送給哪個進程,就填寫目的進程的進程號。 –int tag:消息標籤,接收方需要有相同的消息標籤才能接收該消息。 –MPI_Comm comm:通訊域。表示你要向哪個組發送消息。

參數說明
6. int MPI_Recv( void *buff, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
–void *buff:你接收到的消息要保存到哪個變數里。 –int count:你接收消息的消息的個數(注意:不是長度,例如你要發送一個int整數,這裡就填寫1,如要是發送「hello」字元串,這裡就填寫6(C語言中字元串未有一個結束符,需要多一位))。它是接收數據長度的上界. 具體接收到的數據長度可通過調用MPI_Get_count 函數得到。 –MPI_Datatype datatype:你要接收的數據類型,這裡需要用MPI定義的數據類型,可在網上找到,在此不再羅列。 –int dest:接收端進程號,你要需要哪個進程接收消息就填寫接收進程的進程號。 –int tag:消息標籤,需要與發送方的tag值相同的消息標籤才能接收該消息。 –MPI_Comm comm:通訊域。 –MPI_Status *status:消息狀態。接收函數返回時,將在這個參數指示的變數中存放實際接收消息的狀態資訊,包括消息的源進程標識,消息標籤,包含的數據項個數等。
示例
基本函數都已經介紹完,現在我們來用一個示例來加強對這些基本函數的理解。
#include <stdio.h> #include <string.h> #include "mpi.h" void main(int argc, char* argv[]) { int numprocs, myid, source; MPI_Status status; char message[100]; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myid); MPI_Comm_size(MPI_COMM_WORLD, &numprocs); if (myid != 0) { //非0號進程發送消息 strcpy(message, "Hello World!"); MPI_Send(message, strlen(message) + 1, MPI_CHAR, 0, 99, MPI_COMM_WORLD); } else { // myid == 0,即0號進程接收消息 for (source = 1; source < numprocs; source++) { MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status); printf("接收到第%d號進程發送的消息:%sn", source, message); } } MPI_Finalize(); } /* end main */
運行結果如下圖所示

可以看到,當筆者開啟四執行緒運行時,1-3號進程發送消息,0號進程接收到消息並列印;當筆者開啟八執行緒運行時,1-7號進程發送消息,0號進程接收到消息並列印。

本文使用的是標準阻塞接收發送的方式。消息傳遞是MPI的特性,也是我們學習的難點。這我們學習MPI必須掌握的。
消息發送與接收函數的參數的一些重要說明。
1.MPI標識一條消息的資訊包含四個域:
- – 源:發送進程隱式確定,進程rank值唯一標識.
- – 目的:Send函數參數確定.
- – Tag:Send函數參數確定, (0,UB) 232-1.
- – 通訊子:預設MPI_COMM_WORLD
- • Group:有限/N, 有序/Rank [0,1,2,…N-1]
- • Contex:Super_tag,用於標識該通訊空間.
2. buffer的使用
- buffer必須至少可以容納count個由datatype指明類型的數據. 如果接收buf太小, 將導致溢出、 出錯
3. 消息匹配
- – 參數匹配source,tag,comm/dest,tag,comm.
- – Source == MPI_ANY_SOURCE: 接收任意處理器來的數據(任意消息來源).
- – Tag == MPI_ANY_TAG: 匹配任意tag值的消息(任意tag消息).