MPI
MPI(Message passing Interface) 消息传递接口
利用MPI就可以再集群中实现大规模的数据交换来解决单节点计算力不足的问题
基本概念(Concept)
MPI中的基本名词概念
进程(Process)
一个程序(program),是由多个在相同或不同计算机节点上运行的进程(process)或线程(thread)构成,在MPI程序中,将一个能够独立参与通讯的个体称之为一个进程
进程组(Group of processes)
顾名思义,就是多个进程的集合,为了区分每个进程,在进程组中会用到序号(rank)来标识每一个进程,序号范围从0开始到进程的数量-1。 其中rank 0 一般被作为主程序(master)。
通信器(communicator, 在代码中为 MPI_Comm)
通信器是用来连接MPI中的进程组的,是由通信器为每一个进程提供独立的标识符,MPI所有通信必须在某个通信器中进行。
MPI所能理解的有两种通信方式单组内部通信操作(域内通信single group intracommunicator) 和 双边内部通信(域间bilateral intercommunicator communication),前者用于同一进程中进程间的通信,后者用于分属不同进程的进程间的通信。
MPI系统在一个MPI程序运行时会自动创建两个通讯器
MPI_COMM_WORLD,包含MPI程序中所有进程
MPI_COMM_SELF,指单个进程自己所构成的通信器
序号/秩(Rank)
进程的标识,用来区分不同的进程。MPI 的进程由进程组/序号或通信器/序号唯一确定。
消息(Message)
MPI 程序中在进程间传递的数据。它由通信器、源地址、目的地址、消息标签和数据构成。
通信(communication)
通信是指在进程之间进行消息的收发、同步等操作。
MPI基本函数/接口
总共有6个函数,分成3种类型
-
开始和结束MPI的接口:
MPI_Init , MPI_Finalize
-
获取进程状态的接口:
MPI_Comm_rank , MPI_Comm_size
-
传输数据的接口:
MPI_Send , MPI_Recv
-
MPI_Init(int* argc ,char** argv[] )
初始化MPI执行环境通常也是第一个被调用的MPI函数,建立多个MPI进程之间的联系,为后续通信做准备。 -
MPI_Finalize(void)
结束MPI执行环境。 表明并行代码的结束,结束除主进程外其它进程。
串行代码仍可在主进程(rank = 0)上运行, 但不能再有MPI函数(包括MPI_Init())。 -
MPI_Comm_size(MPI_Comm communicator ,int* size)
用来标识相应进程组中有多少个进程,获得进程个数size
获得通信子communicator中规定的group包含的进程的数量 -
MPI_Comm_rank(MPI_Comm communicator ,int* rank)
用来标识各个MPI进程的,给出调用该函数的进程的进程号,返回整型的错误值。两个参数:MPI_Comm类型的通信域,标识参与计算的MPI进程组; &rank返回调用进程中的标识号相当于进程的ID。 -
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
:整型,消息标志,接收方需要有相同的信息标签才能接受该消息。 如果A需要发给B多种消息,B就可以通过tag
定位特定信息,其余信息就会被缓存起来MPI_Comm comm
:MPI进程组所在的通信域, 表示要向哪个组发送信息
含义:向通信域中的dest进程发送数据,数据存放在buf中,类型是datatype,个数是count,这个消息的标志是tag,用以和本进程向同一目的进程发送的其它消息区别开来。
MPI_Recv(void *buff, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
source
:整型,指定接收数据的来源,所以是发送数据进程的进程号;
tag
: 需要与发送方tag值相同才能接受该消息,接收方可以通过tag来筛选数据
status
:消息状态。MPI_Status结构指针,接收函数返回时,将在这个参数指示的变量中存放实际接收消息的状态信息,包括消息的源进程标识,消息标签,包含的数据项个数等。
MPI_Status
结构体
如果接收端status
的参数不为MPI_STATUS_IGNORE
的话,那么MPI_Status
会储存以下三个数据
1. 发送端秩. 发送端的秩存储在结构体的 MPI_SOURCE
元素中。也就是说,如果我们声明一个 MPI_Status stat
变量,则可以通过 stat.MPI_SOURCE
访问秩。
2. 消息的标签. 消息的标签可以通过结构体的 MPI_TAG 元素访问(类似于 MPI_SOURCE
)
3. 消息的长度. 消息的长度在结构体中没有预定义的元素。相反,我们必须使用 MPI_Get_count
找出消息的长度
MPI_Get_count(
MPI_Status* status,
MPI_Datatype datatype,
int* count)
MPI_Status
的用处是,当接收端以MPI_ANY_SOURCE
和MPI_ANY_TAG
作为参数的话,这种情况下只能通过MPI_Status
来找出消息的实际发送方和标签。
此外,并不能保证 MPI_Recv
能够接收函数调用参数的全部元素。 相反,它只接收已发送给它的元素数量(如果发送的元素多于所需的接收数量,则返回错误。) MPI_Get_count
函数用于确定实际的接收量。
const int MAX_NUMBERS = 100;
int numbers[MAX_NUMBERS];
int number_amount;
if (world_rank == 0) {
// 选择随机数量的数传给进程1
srand(time(NULL));
number_amount = (rand() / (float)RAND_MAX) * MAX_NUMBERS;
MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
printf("0 sent %d numbers to 1\n", number_amount);
} else if (world_rank == 1) {
MPI_Status status;
// 从进程0中说到MAX_NUMBERS个数,实际收到数量会小于MAX_NUMBERS的个数
MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD,
&status);
// 再收到信息后,通过status来判断实际收到的数量
MPI_Get_count(&status, MPI_INT, &number_amount);
// 打印收到的数量,和存储再status里的其他数据也就是消息来源和tag
printf("1 received %d numbers from 0. Message source = %d, "
"tag = %d\n",
number_amount, status.MPI_SOURCE, status.MPI_TAG);
}
>>> cd tutorials
>>> ./run.py check_status
mpirun -n 2 ./check_status
0 sent 92 numbers to 1
1 received 92 numbers from 0. Message source = 0, tag = 0
MPI_Status
的功能是在实际收到消息之后再查询消息大小,如果想要在实际接受消息之前就能查询大小我们就要用MPI_Probe
MPI_Probe(
int source,
int tag,
MPI_Comm comm,
MPI_Status* status)
与 MPI_Recv
类似,MPI_Probe
将阻塞具有匹配标签和发送端的消息。 当消息可用时,它将填充 status
结构体。 然后,用户可以使用 MPI_Recv
接收实际的消息
int number_amount;
if (world_rank == 0) {
const int MAX_NUMBERS = 100;
int numbers[MAX_NUMBERS];
// 随机选择发送数据的数量
srand(time(NULL));
number_amount = (rand() / (float)RAND_MAX) * MAX_NUMBERS;
MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
printf("0 sent %d numbers to 1\n", number_amount);
} else if (world_rank == 1) {
MPI_Status status;
// 再接受前先调用MPI_Probe来获取发送端传的数据数量
MPI_Probe(0, 0, MPI_COMM_WORLD, &status);
// 当probe返回时,就可以调用status来判断数量
MPI_Get_count(&status, MPI_INT, &number_amount);
// 然后可以根据数量来动态的分配缓存区域大小
int* number_buf = (int*)malloc(sizeof(int) * number_amount);
// 现在就可以开始接受发送端的数据
MPI_Recv(number_buf, number_amount, MPI_INT, 0, 0,
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("1 dynamically received %d numbers from 0.\n",
number_amount);
free(number_buf);
}
MPI简单示例(打印每一个进程)
#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,分配进程号
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
if