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
");
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
", 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
",
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用来关联他们之前的通信关系。
© Menglong TAN