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<iostream>
int main(int argc,char** argv)
{
    printf("Hello Word!");
    return(0);
}

并行 main.cpp

#include"mpi.h"
#include<iostream>
using namespace std;
int main(int argc,char** argv)
{
    //通过MPI_Init函数进入MPI环境并完成所有的初始化工作,标志并行代码的开始。
    MPI_Init(&argc,&argv);
    cout<<"Hello World!"<<endl;
    //通过MPI_Finalize函数从MPI环境中退出,标志并行代码的结束,如果不是MPI程序最后一条可执行语句,则运行结果不可知。
    MPI_Finalize();
    return(0);
}

代码说明:

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

2.2 通信域包含的进程数

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

示例:

#include<iostream>
#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 <stdio.h>
#include <mpi.h>

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 <stdio.h>
#include <mpi.h>

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<numprocs; source++) {
            // int MPI_Recv(void* msg_buf_p, int buf_size, MPI_Datatype msg_type, int source, int tag, MPI_Comm communicator, MPI_Status *status_p)
            // 发送缓冲区中的信息到目标进程。
            // void* msg_buf_p : 缓冲区的起始地址;
            // int buf_size : 缓冲区大小;
            // MPI_Datatype msg_type : 发送信息的数据类型;
            // int dest :目标进程的id值;
            // int tag : 消息标签;
            // MPI_Comm communicator : 通信子;
            // MPI_Status *status_p : status_p对象,包含实际接收到的消息的有关信息
			//your code here

                             
			MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status);
			//end of your code
			
			printf("%s\n", message);
		}
	}

	MPI_Finalize();
	return 0;
}

2.5 MPI_Reduce

#include <stdio.h>
#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<stdio.h>
#include<mpi.h>

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<stdio.h>
#include<mpi.h>

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<numprocs;i++) {
			printf("Now is process %d's data: ", i);
			for(j=0;j<5;j++) {
				printf("array[%d]=%d\t", j, rbuf[i*5+j]);
			}
			printf("\n");
		}
	}
	
	MPI_Finalize();
	return 0;
} 

2.8 MPI_Scatter

#include<stdio.h>
#include<mpi.h>

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<numprocs*5;i++) {
    		sbuf[i]=i;
		}
	}

	// int MPI_Scatter(void* sendbuf, int sendcount, MPI_Datatype sendtype,
	//                 void* recvbuf, int recvcount, MPI_Datatype recvtype,
	//                 int root, MPI_Comm comm)
	// MPI_SCATTER是MPI_GATHER的逆操作,另外一种解释是根进程通过MPI_Send发送一条消息,这条消息被分成n等份,第i份发送给组中的第i个处理器, 然后每个处理器如上所述接收相应的消息。
	// void* sendbuf   发送缓冲区的起始地址
	// int sendcount   发送的数据个数
	// MPI_Datatype sendtype    发送缓冲区中的数据类型
	// void* recvbuf     接收缓冲区的起始地址
	// int recvcount  待接收的元素个数
	// MPI_Datatype recvtype    接收的数据类型
	// int root       发送进程id
	// MPI_Comm comm       通信子
    // your code here
	MPI_Scatter(sbuf, 5, MPI_INT, rbuf, 5, MPI_INT, source, MPI_COMM_WORLD);	
	// end of your code
	
	printf("Now is process %d: ", myid);
	for(i=0;i<5;i++) {
		printf("array[%d]=%d\t", i, rbuf[i]);
	}
	printf("\n");
	
	
	
	MPI_Finalize();
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值