基于MPI的并行程序设计.doc

上传人:聆听****声音 文档编号:709534 上传时间:2023-04-29 格式:DOC 页数:10 大小:46.50KB
下载 相关 举报
基于MPI的并行程序设计.doc_第1页
第1页 / 共10页
基于MPI的并行程序设计.doc_第2页
第2页 / 共10页
基于MPI的并行程序设计.doc_第3页
第3页 / 共10页
基于MPI的并行程序设计.doc_第4页
第4页 / 共10页
基于MPI的并行程序设计.doc_第5页
第5页 / 共10页
基于MPI的并行程序设计.doc_第6页
第6页 / 共10页
基于MPI的并行程序设计.doc_第7页
第7页 / 共10页
基于MPI的并行程序设计.doc_第8页
第8页 / 共10页
基于MPI的并行程序设计.doc_第9页
第9页 / 共10页
基于MPI的并行程序设计.doc_第10页
第10页 / 共10页
亲,该文档总共10页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

基于MPI的并行程序设计.doc

《基于MPI的并行程序设计.doc》由会员分享,可在线阅读,更多相关《基于MPI的并行程序设计.doc(10页珍藏版)》请在冰点文库上搜索。

基于MPI的并行程序设计.doc

基于MPI的并行程序设计

1、消息传递并行程序的基本原理

1.1消息传递的基本概念

MP(messagepassing)消息传递模型:

采用消息传递模型的程序由一组进程构成,每个进程只能访问本地的(自己)的存储器空间,在不同进程之间的通信通过发送和接收消息来完成。

根据这个定义,在消息传递模型下,不同进程之间的数据传输由发送进程和接收进程共同完成。

由于支持消息通信对系统的硬件和软件的要求都不高,因此消息传递模型在并行程序设计中被广泛采用。

最简单的可以采用消息传递方式编程的并行计算机系统包括多个处理器,每个处理器有自己的存储器,他们用某种形式的互联网络连接在一起。

因此,消息传递模型不仅可以用来编写分布内存并行计算机系统的程序,也可以用来编写集群系统上的程序。

而在共享存储器系统中,消息传递可以用共享存储器来代替互连网络,因此,消息传递模型具有可以适应多种体系结构的要求。

从软件的角度来说,采用消息传递的系统通常以消息传递库的形式出现,库中包含了发送了接收消息所需要的函数。

这使得可以以现有的串行程序语言为基础来开发消息传递的程序。

一个消息传递的库从严格意义上来说,只需要提供两个函数:

一个用来发送消息,一个用来接收消息,但事实上,绝大多数最近出现的消息传递库比如CMMD,NX,MPL和MPI都提供了额外的函数调用来进行复杂的通信操作,比如在一组处理器中进行集合通信的操作。

2、点到点通信

主要的点到点通信操作是SEND和RECEIVE。

SEND把一条消息从一个处理器发送到另外一个,而RECEIVE则读取来自其他处理器的消息。

在最简单的情况下,对SEND和RECEIVE的调用不会立即返回,除非实际的操作已经完成(消息发送完毕或者消息接收完毕)。

这种形式的操作被称为阻塞SEND和RECEIVE。

一个阻塞的SEND只有在对应的RECEIVE操作已经被调用(不一定结束)并且消息已经被发送的情况下才会顺利返回。

同样的,一个阻塞RECEIVE只有在对应的SEND操作已经调用并且消息已经成功地接收的情况下才会顺利返回(这里暂时不讨论发生异常比如调用出错的情况)。

对阻塞操作来说,每次处理器进行SEND和RECEIVE调用时它需要等待的时间不仅包括传输数据所需的时间,还包括对应的处理器对匹配的操作进行调用的时间。

因此在消息传递过程中,除非SEND和RECEIVE操作同时调用,总有一些处理器处在等待状态,这增加了程序执行的额外开销。

SEND和RECEIVE操作通常有另外一种形式,称为非阻塞式通信。

一个非阻塞SEND和RECEIVE操作并不等待实际的消息传送完全结束(只要底层系统保证会完成剩下的工作就行),它们就开始执行下面的操作(当然这些操作不能和SEND/RECEIVE的结果有依赖关系)。

在适当的时候,处理器可以检查刚才的非阻塞SEND/RECEIVE调用是否完成了,以决定下一步的操作。

除了点对点通信,还有集合通信。

3、MPI程序设计基础

3.1MPI简介

MPI(MessagePassingInterface)是一种基于消息传递的并行程序设计标准,它明确定义了一整套用户接口,而对于具体实现,除了给出了建议以外,并没有太多的限制。

由于它在标准化方面所进行的努力,它已经成为了消息传递并行程序程序设计的代表和事实上的标准。

在MPI标准的制定过程中,制定者希望MPI能够达到下面的三个目标:

较高的通信性能

较好的程序可移植性

可以满足消息传递程序设计的各种要求

由于MPI是一个库而不是一种程序语言,因此对MPI的使用必须和特定的程序语言结合起来进行。

通常情况下,对一个MPI的实现,对FORTRAN和C语言的支持是基本的要求。

3.2MPI的实现

在实际的系统中,MPI以程序库的形式出现,通过库函数接口给用户提供MPI规范定义的功能。

这样的库被称为MPI实现。

SUN公司提供的SUNClusterTools即是一个MPI的实现

一个简单的C语言版MPI程序

//simple.c

#include“mpi.h”

#inlucde

#include

intmain(intargc,char**argv)

{

intmyid,numprocs;

intnamelen;

charprocessor_name[MPI_MAX_PROCESSOR_NAME];

//MPIInitializationphase

MPI_Init(&argc,&argv);

MPI_Comm_rank(MPI_COMM_WORLD,&myid);

MPI_Comm_size(MPI_COMM_WORLD,&numprocs);

MPI_Get_processor_name(processor_name,&namelen);

//Theworkingphase

Printf(“ThisisProcess%dof%don%s\n”,myid,numprocs,processor_name);

//MPIFinalizationphase

MPI_Finalize;

}

用mpicc命令将这个程序编译:

mpicc–osimplesimple.c

然后用mpirun来运行这个程序(假定已经设置好了配置文件,关于MPI环境的配置可以参考第三章SunClustertools的安装配置)。

mpirun–np4simple

为这个程序指定四个工作进程,如果这个程序在一个集群系统的某台计算机cluster1上运行,它的输出结果(可能)如下:

ThisisProcess0of4oncluster1

ThisisProcess2of4oncluster1

ThisisProcess1of4oncluster1

ThisisProcess3of4oncluster1

如果这个程序在一个集群系统的四台不同计算机cluster1-cluster4上运行,它的输出结果(可能)如下:

ThisisProcess0of4oncluster1

ThisisProcess2of4oncluster3

ThisisProcess1of4oncluster2

ThisisProcess3of4oncluster4

从上边的例子可以看出,编写一个MPI程序,在配置好MPI的环境之后,有以下特点:

1)包含MPI的头文件声明,如例子中的#include"mpi.h"

2)MPI相关变量的声明,如例子中的intmyid,numprocs;等,声明了程序的id和总的进程数

3)MPI初始化过程,MPI函数调用,获得程序必须的系统信息,如例子中的

MPI_Init(&argc,&argv);

3)通过MPI的通信,包含在程序体中,如例子中的MPI_Comm_rank(MPI_COMM_WORLD,&myid);是用来得到当前进程号的通信接口,MPI的主要功能即提供进程间通信的接口。

5)MPI结束过程,释放系统资源。

如例子中的MPI_Finalize;

MPI程序的一些惯例

MPI命名规范

所有MPI的名字都有前缀“MPI_”,不管是常量、变量还是函数调用的名字都是这样。

而在用户程序中自己定义的常量、变量、过程和函数调用不要以“MPI_”开头,以免造成错误。

FORTRAN形式的MPI调用,一般全为大写(虽然FORTRAN语法不区分大小写),而C形式的MPI调用,则为MPI_Xxxx_xxx的形式(如上边的MPI_Comm_rank())。

MPI返回值

所有MPI的FORTRAN子程序在最后的参数中都有一个返回代码,对于成功的函数调用,返回代码包含的值为MPI_SUCCESS,对于失败的函数调用,其错误代码依赖于具体的MPI实现。

一些MPI操作是函数,没有这个参数,对MPI的C语言绑定,这个返回代码由函数的返回值直接给出,而不再作为参数。

3.3MPI调用接口

在MPI-1中,共有128个调用借口,而在MPI-2中有287个,SUNClusterTools是MPI-2标准的一个实现。

MPI的基本功能以MPI初始化,MPI结束,得到当前进程号,得到通信域中包含的进程数,发送消息,消息接收六个接口最为常用,下边分别以C语言的实现做基本的介绍。

MPI的参数有三种,分别是IN,OUT和INOUT。

它们表达的含义分别是:

IN(输入):

调用部分传递给MPI的参数,MPI除了使用该参数外在系统内部不会对该参数进行任何修改;

OUT(输出):

MPI使用这个参数来给调用者返回结果,系统内部不会使用调用者设定的初值。

INOUT(输入输出):

该参数首先被调用者用作IN参数,然后被MPI用作输出参数。

MPI初始化

intMPI_Init(int*argc,char**argv)

MPI_Init()为MPI程序的第一个调用,它完成MPI程序的初始化工作。

在调用其他MPI函数之前,所有的MPI程序都需要调用这个函数。

MPI结束

intMPI_Finalize(void)

MPI_Finalize()是MPI程序的最后一个调用,它结束MPI程序的运行,应该是MPI程序最后一条可执行语句,否则程序的运行结构可能会出现无法预料的结果。

得到当前进程号

intMPI_Comm_rank(MPI_Commcomm,int*rank)

其中comm为通信域描述符,rank返回调用进程在这个通信域中的进程号。

对MPI的C语言绑定来说,进程号从0开始。

同一MPI程序中的所有进程被系统赋予不同的进程号,这个进程号使得进程可以进行自我定位,将自己和其他的进程区别开,从而同一程序中的各进程可以相互协作共同完成计算任务。

在SPMD中,它通常和MPI_Comm_size()调用结合起来来确定每个工作进程的任务分配。

得到通信域中包含的进程数

intMPI_Comm_size(MPI_Commcomm,int*size)

其中comm为通信域描述符,size返回调用进程在这个通信域中的工作进程数。

发送消息

intMPI_Send(void*buf,intcount,MPI_Datatypedatatype,intdest,inttag,MPI_Commcomm)

这是MPI的基本发送函数。

MPI_SEND函数将发送缓冲区buf中的count个datatype数据类型的数据发送到目的进程dest(目的进程在通信域comm中的进程号),tag为消息标签,通过使用它,可以把本次发送的消息与这两个进程间其他的消息区分开。

MPI_SEND操作中指定的发送缓冲区buf是一个count个类型为datatype的数据组成的连续的数据块,buf是这个数据块的首地址。

需要注意的是count不是字节数。

其中的data_type数据类型可以是MPI的预定义类型,也可以是用户在MPI预定义类型基础上构造出来的自定义类型。

消息接收

intMPI_Recv(void*buf,intcount,MPI_Datatypedatatype,intsource,inttag,MPI_Commcomm,MPI_Status*status)

这是MPI的基本接收函数。

MPI_RECV从指定的通信域comm中进程号为source的进程接收count个数据类型为datatype的数据,接收的数据被存放在接收缓冲区buf中,所接收的消息的消息标志必须为tag。

status用来向调用者返回本次接收操作的状态。

需要注意的是,调用者在调用这个函数以前,需要事先为接收缓冲区分配空间,buf是这个缓冲区的首地址。

需要注意的是,接收缓冲区的大小必须足以容纳接收到的消息,否则MPI会发生缓冲区溢出,从而出错。

在MPI函数向缓冲区中写入消息时,只有对应于所接收消息的缓冲区会被修改,其余部分不会改变。

数据类型datatype和count的含义和上面的MPI_SEND相同。

MPI返回状态变量类型

MPI_status是MPI定义的一种数据类型,使用之前调用者需要为它分配空间。

在Recv中它用来给调用者提供关于接收到的消息的相关信息。

在C语言中它是一个结构类型,最简单的定义如下:

typedefstructMPI_Status{

intMPI_SOURCE;

intMPI_TAG;

intMPI_ERROR;

}

MPI_Status.MPI_SOURCE为消息的发送者的进程号,MPI_Status.MPI_TAG时消息的消息标志,另外MPI_Status.MPI_ERROR包含了本次接收操作的错误代码。

MPI_Status还可以有其它的域,比如通过MPI_Get_count可以知道本次接收实际接收到的数据个数。

MPI中的数据类型

MPI预定义C数据类型与C类型的对应关系

MPI_CHARchar

MPI_SHORTshortint

MPI_INTint

MPI_LONGlongint

MPI_UNSIGNED_CHARunsignedchar

MPI_UNSIGNED_SHORTunsignedshortint

MPI_UNSIGNEDunsignedint

MPI_UNSIGNED_LONGunsignedlongint

MPI_FLOATfloat

MPI_DOUBLEdouble

MPI_LONG_DOUBLElongdouble

MPI_BYTE无对应类型

MPI_PACKED无对应类型

可选的MPI预定义C数据类型

MPI_LONG_LONG_INTlonglongint

MPI通信的不同模式

MPI共有4种不同的通信模式,分别是标准通信模式(standardmode),缓冲通信模式(bufferedmode),同步通信模式(synchronousmode),和就绪通信模式(ready-mode)。

四种通信模式的区别主要在以下几个方面:

对发送数据的缓冲;接收操作和发送操作的执行顺序;发送操作的返回时刻;对发送安全性的保证,详细可参考MPI的标准。

这里再补充两个MPI的高级内容以供我们后边的例子使用,更多MPI的高级内容请参考

MPI的标准

广播

intMPI_Bcast(void*buffer,intcountMPI_Datatype,introot,MPI_Commcomm);

buffer通信消息缓冲区起始地址(对发送处理器是发送缓冲区,对接收处理则是接收缓冲区)

count需要广播(接收)的数据个数

datatype需要广播(接收)的数据类型

root广播源(称为根处理器)

comm通信域

归约

intMPI_Reduce(void*sendbuf,void*recvbuf,intcount,MPI_Datatypedatatype,MPI_Opop,introot,MPI_Commcomm);

sendbuf发送缓冲区起始地址(待归约数据)

recvbuf接收缓冲区起始地址(root中存放最后结果的地址)

count发送缓冲区中的数据个数(归约数组的长度)

datatype归约的数据类型

op归约操作类型

root根处理器

comm通信域

MPI预定义的归约操作类型如下:

对使用MPI计算PI值的关键部分注释

#include"mpi.h"

#include

#include

doublef(double);

doublef(doublea)

{

return(4.0/(1.0+a*a));

}

intmain(intargc,char*argv[])

{

intn,myid,numprocs,i;

doublePI25DT=3.141592653589793238462643;

doublemypi,pi,h,sum,x;

doublestartwtime=0.0,endwtime;

intnamelen;

charprocessor_name[MPI_MAX_PROCESSOR_NAME];

MPI_Init(&argc,&argv);

MPI_Comm_size(MPI_COMM_WORLD,&numprocs);

MPI_Comm_rank(MPI_COMM_WORLD,&myid);

MPI_Get_processor_name(processor_name,&namelen);

fprintf(stdout,"Process%dof%dison%s\n",

myid,numprocs,processor_name);

fflush(stdout);

n=10000;/*default#ofrectangles*/

if(myid==0)

startwtime=MPI_Wtime();

MPI_Bcast(&n,1,MPI_INT,0,MPI_COMM_WORLD);

/*广播n的值,&n为n的地址,1表示只广播一个数据,MPI_INT表明要广播的是int型数据,0表示广播源为id为0的处理器,通信域为所有的计算机*/

h=1.0/(double)n;

sum=0.0;

/*Aslightlybetterapproachstartsfromlargeiandworksback*/

for(i=myid+1;i<=n;i+=numprocs)

/*每一个进程计算一部分矩形的面积若进程总数numprocs为4将0-1区间划分为100个

矩形则各个进程分别计算矩形块

0进程15913...97

1进程261014...98

2进程371115...99

3进程481216...100

*/

{

x=h*((double)i-0.5);

sum+=f(x);

}

mypi=h*sum;

MPI_Reduce(&mypi,&pi,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);

/*将部分和累加得到所有矩形的面积,该面积即为PI的近似值*/

if(myid==0){

endwtime=MPI_Wtime();

printf("piisapproximately%.16f,Erroris%.16f\n",

pi,fabs(pi-PI25DT));

printf("wallclocktime=%f\n",endwtime-startwtime);

fflush(stdout);

}

MPI_Finalize();

return0;

}

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > IT计算机 > 电脑基础知识

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2