2. MPI简介

2. MPI简介

MPI其实就是一个“库”,共有上百个函数调用接口,但是最常用的只有6个,只需通过使用这6个函数就可以完成几乎所有的通信功能。这六个函数分别为:MPI_Init函数、MPI_Comm_size函数、MPI_Comm_size函数、MPI_Send函数、MPI_Recv函数、MPI_Finalize函数。

  • MPI是基于消息传递的并行计算模式,与 pthread,openMP等共享内存不同。
  • MPI程序中,既有串行执行的程序,也有并行执行的程序。其中,并行的部分全部放在MPI_Init(&argc,&argv)和MPI_Finalize()内部。
  • MPI编译和运行命令
mpicxx -o main main.cpp //编译
mpirun -np 4 ./main  //运行,其中-np指定开启的进程数
  • MPI主要应用于集群上

2.1 使用MPI实现“Hello Word”(C++语言)

串行 main.cpp

#include
int main(int argc,char** argv)
{
    printf("Hello Word!");
    return(0);
}

并行 main.cpp

#include"mpi.h"
#include
using namespace std;
int main(int argc,char** argv)
{
    //通过MPI_Init函数进入MPI环境并完成所有的初始化工作,标志并行代码的开始。
    MPI_Init(&argc,&argv);
    cout<<"Hello World!"<

代码说明:

MPI_Init(&argc,&argv);MPI_Finalize();为并行部分的开始语句和结束语句,在MPI_Finalize();后的语句将执行串行计算。

2.2 通信域包含的进程数

MPI_COMM_SIZE(comm,size)
IN comm 通信域(句柄)
OUT size 通信域comm内包括的进程数

示例:

#include
#include"mpi.h"
using namespace std;
int main(int argc, char **argv)
{
	int numprocs; //用于存储通信域的进程数
	MPI_Init(&argc, &argv);
    // int MPI_Comm_size(MPI_Comm comm, int *rank)
    // 获取指定通信域的进程个数。
    // 其中,第一个参数是通信子,第二个参数返回进程的个数。
	//your code here
    MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
	printf("Hello World! The number of processes is %d\n",numprocs);
    //end of your code    
	MPI_Finalize();
	return 0;
}

这一调用返回给定的通信域中所包含的进程的个数,不同的进程通过这一调用得知在给定的通信域中一共有多少个进程在执行。

2.3 当前进程标识

MPI_COMM_RANK(comm,rank)
IN comm 该进程所在的通信域
OUT rank 调用进程在comm中的标识号

示例:

#include 
#include 

int main(int argc, char **argv)
{
	int myid, numprocs;
	MPI_Init(&argc, &argv);

    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

    // int MPI_Comm_rank(MPI_Comm comm, int *rank)
    // 获得当前进程在指定通信域中的编号,将自身与其他程序区分。
    // 其中,第一个参数是通信子,第二个参数返回进程的编号。
	//your code here
    MPI_Comm_rank(MPI_COMM_WORLD, &myid); 
	printf("Hello World!I'm rank %d of %d\n", myid, numprocs);
    //end of your code
	MPI_Finalize();
	return 0;
}

这一调用返回进程在给定的通信域中的进程标识号,有了这一标识号,不同的进程就可以将自身和其它的进程区别开来,实现各进程的并行和协作。

2.4 消息发送和接收

2.4.1 消息发送:

MPI_SEND(buf,count,datatype,dest,tag,comm)
IN buf 发送缓冲区的起始地址(可选类型)
IN count 将发送的数据个数(非负整数)
IN datatype 发送数据的数据类型(句柄)
IN dest 目的进程的标识号(整型)
IN tag 消息标志(整型)
IN comm 通信域(句柄)

MPI_SEND将缓冲区中的count个datatype数据类型发送到目的进程,目的进程在通信域的标识号为dest,本次发送的消息标志为tag,使用这一标志,就可以把本次发送的消息和本进程向同一目的进程发送的其他消息区别开来。

MPI_SEND操作指定的发送缓冲区是由count个类型为datatype的连续数据空间组成起始地址为buf,注意这里不是以字节计数而是以数据类型为单位指定消息的长度,这样就独立于具体的实现并且更接近于用户的观点。

其中datatype数据类型可以是MPI的预定义类型,也可以是用户自定义的类型。通过使用不同的数据类型调用MPI_SEND,可以发送不同类型的数据。

2.4.2 消息接收:

MPI_RECV(buf,count,datatype,source,tag,comm,status)
OUT buf 接收缓冲区的起始地址(可选数据类型)
IN count 最多可接收的数据的个数(整型)
IN datatype 接收数据的数据类型(句柄)
IN source 接收数据的来源即发送数据的进程的进程标识号(整型)
IN tag 消息标识 与相应的发送操作的表示相匹配相同(整型)
IN comm 本进程和发送进程所在的通信域(句柄)
OUT status 返回状态 (状态类型)

MPI_RECV从指定的进程source接收消息,并且该消息的数据类型和消息标识和本接收进程指定的datatype和tag相一致,接收到的消息所包含的数据元素的个数最多不能超过count。

接收缓冲区是由count个类型为datatype的连续元素空间组成 由datatype指定其类型。起始地址为buf,接收到消息的长度必须小于或等于接收缓冲区的长度。这是因为如果接收到的数据过大,MPI没有截断,接收缓冲区会发生溢出错误。因此编程者要保证接收缓冲区的长度不小于发送数据的长度。如果一个短于接收缓冲区的消息到达,那么只有相应于这个消息的那些地址被修改。count可以是零,这种情况下消息的数据部分是空的。

其中datatype数据类型可以是MPI的预定义类型,也可以是用户自定义的类型。通过指定不同的数据类型调用MPI_RECV,可以接收不同类型的数据。

返回状态status

返回状态变量status用途很广,它是MPI定义的一个数据类型。使用之前需要用户为它分配空间 。

在C实现中状态变量是由至少三个域组成的结构类型,这三个域分别是MPI_SOURCE,MPI_TAG和MPI_ERROR。它还可以包括其它的附加域。这样通过对status.MPI_SOURCE、status.MPI_TAG和status.MPI_ERROR的引用,就可以得到返回状态中所包含的发送数据进程的标识。发送数据使用的tag标识和本接收操作返回的错误代码 。

除了以上三个信息之外,对status变量执行MPI_GET_COUNT调用可以得到接收到的消息的长度信息。

示例:

#include 
#include 

int main(int argc, char **argv)
{
	int myid, numprocs, 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) {
    	strcpy(message, "hello world!");
    	
        // int MPI_Send(void* msg_buf_p, int msg_size, MPI_Datatype msg_type, int dest, int tag, MPI_Comm communicator)
        // 发送缓冲区中的信息到目标进程。
        // void* msg_buf_p : 发送缓冲区的起始地址;
        // int buf_size : 缓冲区大小;
        // MPI_Datatype msg_type : 发送信息的数据类型;
        // int dest :目标进程的id值;
        // int tag : 消息标签;
        // MPI_Comm communicator : 通信子;
    	//your code here
    	MPI_Send(message, strlen(message)+1, MPI_CHAR,0, 99, MPI_COMM_WORLD);
    	//end of your code
	}
	else { //myid == 0
		for(source=1; source

2.5 MPI_Reduce

#include 
#include "mpi.h"

int main(int argc, char **argv)
{
	int myid, numprocs;
	double local_num = 3.0; 

	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    double global_num;
    
	// int MPI_Reduce(void * input_data_p, void * output_data_p, int count, MPI_Datatype datatype, MPI_Op operator, int dest_process, MPI_Comm comm)
	// 规约函数,所有进程将待处理数据通过输入的操作子operator计算为最终结果并将它存入目标进程中。
	// void * input_data_p : 每个进程的待处理数据存放在input_data_p中; 
	// void * output_data_p : 存放最终结果的目标进程的地址;
	// int count : 缓冲区中的数据个数;
	// MPI_Datatype datatype : 数据项的类型;
	// MPI_Op operator : 操作子,例如加减;
	// int dest_process : 目标进程的编号;
    //your code here
   MPI_Reduce(&local_num, &global_num,1, MPI_DOUBLE, MPI_SUM,0, MPI_COMM_WORLD); 
    //end of your code
    
    if(myid == 0) {
    	printf("Total sum = %f, avg = %f\n", global_num, global_num / numprocs);
	}

	MPI_Finalize();
	return 0;
}

2.6 MPI_Bcast

#include
#include

int main(int argc, char **argv)
{
	int myid, numprocs;
	int source = 0;
	int array[5]={1,2,3,4,5};
	int i;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    if(myid == source) {
        for(i = 1; i <= 5; i++)
            array[i] = i;
    }
    
	// int MPI_Bcast(void* buffer, int count, MPI_Datatype datatype, int source, MPI_Comm comm)
	// 广播函数,从一个id值为source的进程将一条消息广播发送到通信子内的所有进程,包括它本身在内。
	// void*  buffer    缓冲区的起始地址; 
	// int   count     缓冲区中的数据个数; 
	// MPI_Datatype datatype   缓冲区中的数据类型; 
	// int   source     发送信息的进程id; 
	// MPI_Comm comm      通信子;
    //your code here
    MPI_Bcast(array,5, MPI_INT,source, MPI_COMM_WORLD);  
    //end of your code
    
    if(myid != source) {
    	printf("In process %d, ", myid);
        for(i = 0; i < 5; i++)
            printf("arr[%d]=%d\t", i, array[i]);
        printf("\n");
	}

	MPI_Finalize();
	return 0;
}

2.7 MPI_Gather

#include
#include

int main(int argc, char **argv)
{
	int myid, numprocs;
	int dest = 0;
	int array[5]={1,2,3,4,5};
	int *rbuf; 
	int i,j;

	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    if(myid == dest) {
    	rbuf=(int *)malloc(numprocs*5*sizeof(int));
	}

	// int MPI_Gather(void* sendbuf, int sendcount, MPI_Datatype sendtype, 
	// 			void* recvbuf, int recvcount, MPI_Datatype recvtype, 
	// 			int root, MPI_Comm comm)
	// 收集函数,根进程(目标进程)从所有进程(包括它自己)收集发送缓冲区的数据,根进程根据发送这些数据的进程id将它们依次存放到自已的缓冲区中.
	// void* sendbuf    发送缓冲区的起始地址
	// int sendcount    发送缓冲区的数据个数
	// MPI_Datatype sendtype    发送缓冲区的数据类型
	// void* recvbuf    接收缓冲区的起始地址
	// int recvcount    待接收的元素个数
	// MPI_Datatype recvtype    接收的数据类型
	// int root    接收进程id 
	// MPI_Comm comm    通信子
	//your code here
    MPI_Gather(array, 2, MPI_INT, rbuf, 5, MPI_INT, dest, MPI_COMM_WORLD);
	//end of your code
	
	if(myid == dest) {
		for(i=dest+1;i

2.8 MPI_Scatter

#include
#include

int main(int argc, char **argv)
{
	int myid, numprocs;
	int source = 0;
	int *sbuf;
	int rbuf[5]; 
	int i;
	
	MPI_Init(&argc, &argv);
	
	MPI_Comm_rank(MPI_COMM_WORLD, &myid);
	MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    
    if(myid == source) {
    	sbuf=(int *)malloc(numprocs*5*sizeof(int));
    	
    	for(i=0;i

你可能感兴趣的:(HPC)