author: Menglong TAN; email: tanmenglong_at_gmail; twitter/weibo: @crackcell; source:http://blog.crackcell.com/posts/2013/07/15/mpi_quick_start.html.
Table of Contents
1 前言
不知道為啥,MPI的入門教程似乎很少,也不太明了。今天看了一些教程,整理一下入門需要知道的知識點。
2 開發環境設置
環境:debian sid 安裝開發環境:
$ sudo apt-get install openmpi-bin openmpi-doc libopenmpi-dev gcc g++
3 Learn by example
3.1 例子1:Hello world
#include <iostream> #include <mpi/mpi.h> using namespace std; int main(int argv, char* argc[]){ MPI_Init(&argv, &argc); cout << "hello world" << endl; MPI_Finalize(); return 0; }
編譯:
$ mpicxx -o hello.exe hello.cpp
運行:
$ mpirun -np 10 ./hello.exe
- -np 10 參數制定了運行了程序的10個拷貝
3.2 代碼結構
我們來看代碼,MPI程序的結構一般是:
- 頭文件、全局定義
- 初始化MPI環境:MPI_Init()
- 分布式代碼
- 終止MPI環境:MPI_Finalize()
- 結束
3.3 一些基本的API
3.3.1 初始化環境:MPI_Init
#include <mpi.h> int MPI_Init(int *argc, char ***argv)
3.3.2 是否初始化:MPI_Initialized
#include <mpi.h> int MPI_Initialized(int *flag)
3.3.3 終止環境:MPI_Finalize
#include <mpi.h> int MPI_Finalize()
3.3.4 獲取進程數:MPI_Comm_size
獲取一個communicator中的進程數
#include <mpi.h> int MPI_Comm_size(MPI_Comm comm, int *size)
如果communicator是MPI_COMM_WORLD,那就是當前程序能用的所有進程數
3.3.5 獲取當前進程id:MPI_Comm_rank
#include <mpi.h> int MPI_Comm_rank(MPI_Comm comm, int *rank)
3.3.6 獲取程序運行的主機名:MPI_Get_processor_name
#include <mpi.h> int MPI_Get_processor_name(char *name, int *resultlen)
3.3.7 終止一個communicator的所有進程:MPI_Abort
#include <mpi.h> int MPI_Abort(MPI_Comm comm, int errorcode)
3.4 例2:稍微復雜一點
#include <stdio.h> #include <mpi/mpi.h> int main(int argc, char *argv[]) { char hostname[MPI_MAX_PROCESSOR_NAME]; int task_count; int rank; int len; int ret; ret = MPI_Init(&argc, &argv); if (MPI_SUCCESS != ret) { printf("start mpi fail\n"); MPI_Abort(MPI_COMM_WORLD, ret); } MPI_Comm_size(MPI_COMM_WORLD, &task_count); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Get_processor_name(hostname, &len); printf("task_count = %d, my rank = %d on %s\n", task_count, rank, hostname); MPI_Finalize(); return 0; }
運行一下:
$ mpirun -np 3 ./hello3.exe task_count = 3, my rank = 0 on crackcell-vm0 task_count = 3, my rank = 1 on crackcell-vm0 task_count = 3, my rank = 2 on crackcell-vm0
3.5 基本通信API
- MPI提供了消息的緩存機制
- 消息可以以阻塞或非阻塞的方式發送
- 順序性:MPI保證接收者收到消息的順序和發送者的發送順序一致
- 公平性:MPI不保證調度公平性,程序員自己去防止進程飢餓
3.5.1 消息數據類型
為了可移植性,MPI定義了自己的消息數據類型,具體參考1
3.5.2 點對點通信API
- 阻塞發送:MPI_Send
int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
- 非阻塞發送:MPI_Isend
int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
- 阻塞接收:MPI_Recv
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
- 非阻塞接收:MPI_Irecv
int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request)
3.6 例3:阻塞的消息傳遞
#include <stdio.h> #include <mpi/mpi.h> int main(int argc, char *argv[]) { int task_count; int rank; int dest; int src; int count; int tag = 1; char in_msg; char out_msg = 'x'; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &task_count); MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (0 == rank) { dest = 1; src = 1; // 向1發送一個字符,然后等待返回 MPI_Send(&out_msg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD); MPI_Recv(&in_msg, 1, MPI_CHAR, src, tag, MPI_COMM_WORLD, &status); } else if (1 == rank) { dest = 0; src = 0; // 向0發送一個字符,然后等待返回 MPI_Recv(&in_msg, 1, MPI_CHAR, src, tag, MPI_COMM_WORLD, &status); MPI_Send(&out_msg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD); } MPI_Get_count(&status, MPI_CHAR, &count); printf("task %d: recv %d char(s) from task %d with tag %d\n", rank, count, status.MPI_SOURCE, status.MPI_TAG); MPI_Finalize(); return 0; }
3.7 協同通信API
- 協同通信必須涉及同一個communicator中的所有進程
- 協同通信操作的類型
- 同步操作:進程等待同組的其它成員到達某一同步點
- 數據移動操作:broadcast、scatter/gather操作
- 協同計算:某個成員收集其它成員的數據,然后執行某個操作
3.7.1 阻塞直到同組其它任務完成:MPI_Barrier
#include <mpi.h> int MPI_Barrier(MPI_Comm comm)
3.7.2 Broadcast消息:MPI_Bcast
#include <mpi.h> int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm)
3.7.3 散播消息:MPI_Scatter
#include <mpi.h> int MPI_Scatter(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
3.7.4 收集消息:MPI_Gather
#include <mpi.h> int MPI_Gather(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
更多API參考2。
3.8 組和通信器
- 一堆有序的進程組成一個group,每個進程有一個唯一的整數標識
- 一個communicator組織起了一堆需要相互之間通信的進程。MPI_COMM_WORLD包含了所有進程
group用來組織一組進程,communicator用來關聯他們之前的通信關系。