MPI 的 manpages 需要在線查看,或者在 Linux 系統中用 man 查看,不方便。這裏我做了一些對常用函數的分類總結。
基本結構:啓動和終止
#include <mpi.h> // ******************1
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(NULL, NULL); // ******************2
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
char processor_name[MPI_MAX_PROCESSOR_NAME];
int name_len;
MPI_Get_processor_name(processor_name, &name_len);
printf("Hello world from processor %s, rank %d out of %d processors\n",
processor_name, world_rank, world_size);
MPI_Finalize(); // ******************3
}
MPI_Init
MPI_Init(
int* argc,
char*** argv)
所有 MPI 的全局變量或者內部變量都會被創建。舉例來說,一個通訊器 communicator 會根據所有可用的進程被創建出來(進程是我們通過 mpi 運行時的參數指定的),然後每個進程會被分配獨一無二的秩 rank
MPI_Finalize
MPI_Finalize()
用來清理 MPI 環境的。這個調用之後就沒有 MPI 函數可以被調用了。
MPI_Comm_size
MPI_Comm_size(
MPI_Comm communicator,
int* size)
返回 communicator 的大小,也就是 communicator 中可用的進程數量。
MPI_Comm_rank
MPI_Comm_rank(
MPI_Comm communicator,
int* rank)
返回 communicator 中當前進程的 rank。 communicator 中每個進程會以此得到一個從 0 開始遞增的數字作爲 rank 值。rank 值主要是用來指定發送或者接受信息時對應的進程。
MPI_Get_processor_name
MPI_Get_processor_name(
char* name,
int* name_length)
得到當前進程實際跑的時候所在的處理器名字。
點對點的通信:發送和接收
MPI_Send
MPI_Send(
void* data,
int count,
MPI_Datatype datatype,
int destination,
int tag,
MPI_Comm communicator)
本端點發送包含 count 個 datatype 類型的數據 *data 給 rank 爲 destination 的目標端點,數據標籤爲 tag,通訊器爲 communicator(通常爲 MPI_COMM_WORLD
)。
該方法會阻塞直到發送緩存可以被回收。這意味着當網絡可以緩衝消息時,該方法就可以返回;如果網絡不可以緩存消息,就會一直阻塞至遇到匹配的接受方法。
datatype 取值有:
MPI datatype | C equivalent |
---|---|
MPI_SHORT | short int |
MPI_INT | int |
MPI_LONG | long int |
MPI_LONG_LONG | long long int |
MPI_UNSIGNED_CHAR | unsigned char |
MPI_UNSIGNED_SHORT | unsigned short int |
MPI_UNSIGNED | unsigned int |
MPI_UNSIGNED_LONG | unsigned long int |
MPI_UNSIGNED_LONG_LONG | unsigned long long int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_LONG_DOUBLE | long double |
MPI_BYTE | char |
MPI_Recv
MPI_Recv(
void* data,
int count,
MPI_Datatype datatype,
int source,
int tag,
MPI_Comm communicator,
MPI_Status* status)
本端點接受 rank 爲 source (不限制時用 MPI_ANY_SOURCE
)的源端點傳來的,標籤爲 tag (不限制時用 MPI_ANY_TAG
),類型爲 datatype 的數據,數據保存在 *data 中,最大長度爲 count,實際接受的數據長度和 tag 保存在 status 中,status.MPI_SOURCE
爲實際接受的源 rank,status.MPI_TAG
爲實際接受的 tag,通訊器爲 communicator。
該方法會阻塞來接受匹配 source 和 tag 的數據。
MPI_Get_count
參考資料:http://mpitutorial.com/tutorials/dynamic-receiving-with-mpi-probe-and-mpi-status/
MPI_Get_count(
MPI_Status* status,
MPI_Datatype datatype,
int* count)
根據 status 和 datatype,查詢實際接受到了數據個數保存在 *count 中。
MPI_Probe
MPI_Probe(
int source,
int tag,
MPI_Comm comm,
MPI_Status* status)
可以作爲 MPI_Recv
的預熱,通過 status 確定收到的數據大小之後,再分配準確的內存來用 MPI_Recv
接受數據。
示例:
MPI_Status status;
// Probe for an incoming message from process zero
MPI_Probe(0, 0, MPI_COMM_WORLD, &status);
// When probe returns, the status object has the size and other
// attributes of the incoming message. Get the size of the message.
MPI_Get_count(&status, MPI_INT, &number_amount);
// Allocate a buffer just big enough to hold the incoming numbers
int* number_buf = (int*)malloc(sizeof(int) * number_amount);
// Now receive the message with the allocated buffer
MPI_Recv(number_buf, number_amount, MPI_INT, 0, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
笛卡爾拓撲
MPI_Cart_create
int MPI_Cart_create(MPI_Comm comm_old, int ndims, const int dims[],
const int periods[], int reorder, MPI_Comm *comm_cart)
- ndims:指定拓撲結構的維度
- dims[]數組:指定每個維度的大小([3,2] 表示維度 0 的座標爲 0-2,維度 1 的座標爲 0-1)
- periods[]數組:指定拓撲結構中是否有環繞連接,非0表示有,0表示無
- reorder:確定新通信器中的進程是否需要重新排序
獲取屬於通信器 comm_old 的一組進程,創建一個虛擬進程結構。指定的進程數不能大於通信器 comm_old 中的進程總數。不是笛卡爾結構的組成部分的進程獲得的 comm_cart 值爲 MPI_COMM_NULL。
MPI_Cart_coords
int MPI_Cart_coords(MPI_Comm comm, int rank, int maxdims,
int coords[])
通常先用 MPI_Comm_rank
獲得當前進程在笛卡爾通信器中的等級,再用 MPI_Cart_coords
獲得進程的笛卡爾座標。
MPI_Cart_shift
int MPI_Cart_shift(MPI_Comm comm, int direction, int disp,
int *rank_source, int *rank_dest)
- direction:指定維度
- disp:指定通信的方向和距離,負數表示負方向
- rank_source:通信的源進程的等級
- rank_dest:通信的目的進程的等級
計算在數據交換操作中源進程和目標進程的等級。
集體通信:廣播和規約
MPI_Barrier (同步點)
MPI_Barrier(MPI_Comm communicator)
(Barrier,屏障)- 這個方法會構建一個屏障,任何進程都沒法跨越屏障,直到所有的進程都到達屏障。
MPI_Bcast (廣播)
MPI_Bcast(
void* data,
int count,
MPI_Datatype datatype,
int root,
MPI_Comm communicator)
一個廣播發生的時候,一個進程會把同樣一份數據傳遞給一個 communicator 裏的所有其他進程。根節點調用 MPI_Bcast
函數的時候,data 變量裏的值會被髮送到其他的節點上。當其他的節點調用 MPI_Bcast
的時候,data 變量會被賦值成從根節點接受到的數據。
實現使用了一個樹形廣播算法來獲得比較好的網絡利用率。
MPI_Scatter
MPI_Scatter(
void* send_data,
int send_count,
MPI_Datatype send_datatype,
void* recv_data,
int recv_count,
MPI_Datatype recv_datatype,
int root,
MPI_Comm communicator)
root 進程執行該函數時,接收一個數組 send_data,並把元素按進程的秩分發出去,給每個進程發送 send_count 個元素。其他進程包括(root)執行該函數時,收到 recv_count 個 revc_datatype 類型的數據,存放在數組 recv_data 中。
MPI_Gather
MPI_Gather(
void* send_data,
int send_count,
MPI_Datatype send_datatype,
void* recv_data,
int recv_count,
MPI_Datatype recv_datatype,
int root,
MPI_Comm communicator)
所有進程執行該函數時,從 send_datatype 類型的數組 send_data 中取出前 send_count 個元素,發送給 root 進程。root 進程同時還會將從每個進程中收集到的 recv_count 個數據,存放在 recv_data 數組中。
MPI_Gatherv
int MPI_Gatherv(
const void* sendbuf, int sendcount, MPI_Datatype sendtype,
void* recvbuf, const int recvcounts[], const int displs[],
MPI_Datatype recvtype, int root, MPI_Comm comm)
當每個節點傳遞的數據長度不一時,採用這個函數。
- IN sendbuf: starting address of send buffer (choice)
- IN sendcount: number of elements in send buffer (non-negative integer)
- IN sendtype: data type of send buffer elements (handle)
- OUT recvbuf: address of receive buffer (choice, significant only at root)
- IN recvcounts: non-negative integer array (of length group size) containing the number of elements that are received from each process (significant only at root)
- IN displs: integer array (of length group size). Entry i specifies the displacement relative to recvbuf at which to place the incoming data from process i (significant only at root)
MPI_Allgather (多對多)
MPI_Allgather(
void* send_data,
int send_count,
MPI_Datatype send_datatype,
void* recv_data,
int recv_count,
MPI_Datatype recv_datatype,
MPI_Comm communicator)
MPI_Reduce
MPI_Reduce(
void* send_data,
void* recv_data,
int count,
MPI_Datatype datatype,
MPI_Op op,
int root,
MPI_Comm communicator)
每個進程發送容量爲 count 的數組 send_data,root 進程收到後進行 op 操作,存放在容量也爲 count 的數組 recv_data 中。
MPI_Op
操作類型有:
MPI_MAX
- 最大MPI_MIN
- 最小MPI_SUM
- 求和MPI_PROD
- 乘積MPI_LAND
- 邏輯與MPI_LOR
- 邏輯或MPI_BAND
- 位運算的“與”MPI_BOR
- 位運算的“或”MPI_MAXLOC
- 最大值和擁有該值的進程的 rankMPI_MINLOC
- 最小值和擁有該值的進程的 rank
MPI_Allreduce
MPI_Allreduce(
void* send_data,
void* recv_data,
int count,
MPI_Datatype datatype,
MPI_Op op,
MPI_Comm communicator)
Groups 和 Communicators
警告
- MPI 一次可創建的對象是有數量限制的,如果用完了可分配的對象,而不釋放,可能導致運行時錯誤。
- 新建的
MPI_Comm
需要用MPI_Comm_free(MPI_Comm *comm)
來釋放,該函數不能用MPI_COMM_NULL
做參數 - 新建的
MPI_Group
需要用MPI_Group_free(MPI_Group *group)
來釋放
MPI_Comm_split
MPI_Comm_split(
MPI_Comm comm,
int color,
int key,
MPI_Comm* newcomm)
將 comm 中的進程分到新的 newcomm 中,color 相同的進程被分到同一個 newcomm,且根據 key 的大小進行排序,最小的爲 0。
MPI_Comm_create
MPI_Comm_create(
MPI_Comm comm,
MPI_Group group,
MPI_Comm* newcomm)
group 是 comm 的組的子集,利用這個組創建一個新的通訊器 newcomm。非該組內的進程執行函數得到的 newcomm 爲 MPI_COMM_NULL
。釋放資源時要注意!!!
MPI_Comm_group
MPI_Comm_group(
MPI_Comm comm,
MPI_Group *group)
獲得通訊器 comm 對應的組 *group。
MPI_Group_union
MPI_Group_union(
MPI_Group group1,
MPI_Group group2,
MPI_Group* newgroup)
MPI_Group_intersection
MPI_Group_intersection(
MPI_Group group1,
MPI_Group group2,
MPI_Group* newgroup)
MPI_Comm_create_group
MPI_Comm_create_group(
MPI_Comm comm,
MPI_Group group,
int tag,
MPI_Comm* newcomm)
)
group 是通訊器 comm 對應的組的子組,利用這個組創建一個新的通訊器 newcomm。不在這個組內的進程,調用此方法時得到的 newcomm 爲 MPI_COMM_NULL
。釋放資源時要注意!!!
MPI_Group_incl
MPI_Group_incl(
MPI_Group group,
int n,
const int ranks[],
MPI_Group* newgroup)
ranks 數組中有 n 個元素,代表了 group 中的部分進程,用這些進程來創建一個新的組 newgroup。
版權聲明
本文主要內容來自 A Comprehensive MPI Tutorial Resource,一個簡潔的 MPI 入門教程,部分有中文翻譯。
附錄 A:延伸閱讀
- 勞倫斯利弗莫爾國家實驗室的 MPI 教程:https://computing.llnl.gov/tutorials/mpi/