第五章 消息传递接口MPI
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第5章消息传递接口MPI
MPI(消息传递接口)是用于分布式存储器并行计算机的标准编程环境。
MPI的核心构造是消息传递:一个进程将信息打包成消息,并将该消息发送给其他进程。
但是,MPI包含比简单的消息传递更多的内容。
MPI包含一些例程,这些例程可以同步进程、求分布在进程集中的数值的总和、在同一个进程集中分配数据,以及实现更多的功能。
在20世纪90年代早期人们创建了MPI,以提供一种能够运行在集群、MPP、甚至是共享存储器机器中的通用消息传递环境。
MPI以一种库的形式发布,官方的规范定义了对C 和Fortran的绑定(对其他语言的绑定也已经被定义)。
当今MPI程序员主要使用MPI版本1.1(1995年发行)。
在1997年发行了一个增强版本的规范,MPI 2.0,它具有并行I/O、动态进程管理、单路通信和其他高级功能。
遗憾的是,由于它对原有的标准增加了复杂的内容,使得到目前为止,仅有少量的MPI实现支持MPI 2.0。
因为这个原因,我们将在本章中集中介绍MPI1.1。
5.1 MPI编程的基本概念
5.1.1什么是MPI
对MPI的定义是多种多样的,但不外乎下面三个方面,它们限定了MPI的内涵和外延。
1 MPI是一个库,而不是一门语言。
许多人认为MPI就是一种并行语言,这是不准确的。
但是按照并行语言的分类,可以把FORTRAN+MPI或C+MPI,看作是一种在原来串行语言基础之上扩展后得到的并行语言。
MPI库可以被FORTRAN77/C/Fortran90/C++调用,从语法上说,它遵守所有对库函数/过程的调用规则,和一般的函数/过程没有什么区别。
2 MPI是一种标准或规范的代表,而不特指某一个对它的具体实现。
迄今为止,所有的并行计算机制造商都提供对MPI的支持,可以在网上免费得到MPI 在不同并行计算机上的实现,一个正确的MPI程序,可以不加修改地在所有的并行机上运行。
3 MPI是一种消息传递编程模型,并成为这种编程模型的代表和事实上的标准。
MPI 虽然很庞大,但是它的最终目的是服务于进程间通信这一目标的。
关于什么是MPI的问题设计到多个不同的方面。
当我们提到MPI时,不同的上下文中会有不同的含义,它可以是一种编程模型,也可以是一种标准,当然也可以指一类库。
只要全面把握了MPI的概念,这些区别是不难理解的。
5.1.2 MPI的三个主要目的
1 较高的通信性能;
2 较好的程序可移植性;
3 强大的功能。
MPI为自己制定了一个雄心勃勃的目标,总结概括起来,它包括几个在实际使用中都十分重要但有时又是相互矛盾的三个方面,具体地说,包括以下几个方面:
提供应用程序编程接口。
提高通信效率。
措施包括避免存储器到存储器的多次重复拷贝,允许计算和通信的重叠等。
可在异构环境下提供实现。
提供的接口可以方便 C 语言和Fortran 77的调用。
提供可靠的通信接口。
即用户不必处理通信失败。
定义的接口和现在已有接口(如PVM,NX,Express,p4等)差别不能太大,但是允许扩展以提供更大的灵活性。
定义的接口能在基本的通信和系统软件无重大改变时,在许多并行计算机生产商的平台上实现。
接口的语义是独立于语言的。
接口设计应是线程安全的。
MPI提供了一种与语言和平台无关,可以被广泛使用的编写消息传递程序的标准,用它来编写消息传递程序,不仅实用、可移植、高效和灵活,而且和当前已有的实现没有太大的变化。
5.1.3 MPI的语言绑定与实现
在MPI-1中,明确提出了MPI和FORTRAN 77与C语言的绑定,并且给出了通用接口和针对FORTRAN 77与C的专用接口说明,MPI-1的成功说明MPI选择的语言绑定策略是正确和可行的。
Fortran90是FORTRAN的扩充,它在表达数组运算方面有独特的优势,还增加了模块等现代语言的方便开发与使用的各种特征,它目前面临的一个问题是Fortran90编译器远不如FORTRAN 77编译器那样随处可见,但提供Fortran90编译器的厂商正在逐步增多。
C++作为面向对象的高级语言,随着编译器效率和处理器速度的提高,它可以取得接近于C的代码效率,面向对象的编程思想已经被广为接受,因此在MPI-2中,除了和原来的FORTRAN 77和C语言实现绑定之外,进一步与Fortran90和C++结合起来,提供了四种不同的接口,为编程者提供了更多选择的余地。
但是MPI-2目前还没有完整的实现版本。
下面列出了一些主要的MPI免费实现。
表5.1 MPI的一些实现
取得。
更为重要的是,MPICH是一个与MPI-1规范同步发展的版本,每当MPI推出新的版本,就会有相应的MPICH的实现版本,目前MPICH的最新版本是MPICH-1.2.1,它支持部分的MPI-2的特征。
Argonne and MSU(阿尔贡)国家试验室和MSU(密西根州立大学)对MPICH作出了重要的贡献。
在本书中,未特别说明,均指在基于Linux集群的MPICH 实现。
CHIMP是Edinburgh(爱丁堡、英国苏格兰首府)开发的另一个免费MPI实现,是在EPCC(Edinburgh Parallel Computing Centre)的支持下进行的,从ftp:///pub/packages/chimp/release/可以免费下载该软件,CHIMP的开发从1991年到1994年,主要开发人员有Alasdair Bruce, James (Hamish) Mills,和Gordon Smith。
LAM (Local Area Multicomputer)也是免费的MPI实现,由Ohio State University美国俄亥俄州国立大学开发,它目前的最新版本是LAM/MPI 6.3.2,可以从/lam/download/ 下载。
它主要用于异构的计算机网络计算系统。
5.1.4 MPI编程的基本概念
一个MPI并行程序由一组运行在相同或不同计算机/计算结点上的进程或线程构成。
这些进程或线程可以运行在不同处理机上,也可以运行在相同的处理机上。
为统一起见,MPI 程序中一个独立参与通信的个体称为一个进程(process)。
一个MPI进程通常对应于一个普通进程或线程,但是在共享存储/消息传递混合模式程序中,一个MPI 进程可能代表一组UNIX线程。
一个MPI程序中由部分或全部进程构成的一个有序集合称为一个进程组(process group)。
进程组中每个进程被赋予一个该组中的序号(rank),用于在该组中标识该进程,称为进程号。
进程号的取值范围从0开始。
MPI程序中进程间的通信、同步等通过通信器(communicator)进行(一些资料中将通信器翻译成通信子,本书中将统一使用术语通信器)。
MPI的通信器有域内通信器(intra-communicator)和域间通信器(inter-communicator)两种,前者用于属于同一进程组的进程间的通信,后者用于分属两个不同进程组的进程间的通信。
这里只对域内通信器进行介绍,后文中除非特别提及,“通信器”一词一律指域内通信器。
一个通信器由它所包含的进程组及与之相关联的一组属性(例如进程间的拓扑连接关系) 构成。
通信器提供进程间通信的基本环境,MPI 程序中所有通信都必须在特定的通信器中完成。
MPI 程序启动时会自动创建两个通信器,一个称为MPI_COMM_WORLD,它包含程序中的所有进程,另一个称为MPI_COMM_SELF,它是每个进程独自构成的、仅包含自己的通信器。
在MPI 程序中,一个MPI 进程由一个通信器(或进程组) 和进程在该通信器(或进程组) 中的进程号唯一标识。
注意进程号是相对于通信器或进程组而言的:同一个进程在不同的通信器(或进程组)中可以有不同的进程号。
进程号是在通信器(或进程组) 被创建时赋予的。
MPI 系统提供了一个特殊进程号MPI_PROC_NULL,它代表空进程(不存在的进程),与MPI_PROC_NULL进行通信相当于一个空操作,对程序的运行没有任何影响,它的引入可以方便一些程序的编写。
MPI程序中进程间的通信(communications) 通过消息的收发或同步操作完成。
一个消息(message) 指在进程间进行的一次数据交换。
MPI消息包括信封和数据两个部分,信封指出了发送或接收消息的对象及相关信息,而数据是本消息将要传递的内容。
信封和数据又分别包括三个部分。
可以用一个三元组来表示。
信封:<源/目,标识,通信域>
数据:<起始地址,数据个数,数据类型>
以MPI_SEND和MPI_RECV (receive)为例,下图分别给出了它们的信封和数据部分。
图5.1 MPI_SEND语句的消息信封和消息数据
图5.2 MPI_RECV语句的消息信封和消息数据
在消息信封中除了源/目外,为什么还有tag标识呢?这是因为,当发送者发送两个相同类型的数据给同一个接收者时,如果没有消息标识,接收者将无法区别这两个消息。
一个接收操作对消息的选择是由消息的信封管理的。
如果消息的信封与接收操作所指定
的值source,tag和comm相匹配,那么这个接收操作能接收这个消息。
接收者可以给source 指定一个任意值MPI_ANY_SOURCE,标识任何进程发送的消息都可以接收,即本接收操作可以匹配任何进程发送的消息,但其它的要求还必须满足,比如tag的匹配;如果给tag 一个任意值MPI_ANY_TAG,则任何tag都是可接收的。
在某种程度上,类似于统配符的概念。
MPI_ANY_SOURCE和MPI_ANY_TAG可以同时使用或分别单独使用。
但是不能给comm指定任意值。
如果一个消息被发送到接收进程,接收进程有匹配的通信域,有匹配的source (或其source = MPI_ANY_SOURCE),有匹配的tag(或其tag = MPI_ANY_TAG),那么这个消息能被这个接收操作接收。
5.2 MPI的原始数据类型
MPI系统中数据的发送与接收操作都必须指定数据类型。
数据类型可以是MPI系统预定义的,称为原始数据类型,也可以是用户在原始数据类型的基础上自己定义的数据类型。
在此只介绍原始数据类型,自定义数据类型请参看参考文献2。
MPI为C预定义的原始数据类型在表1中给出。
除表中列出的外,某些MPI系统可能支持更多的原始数据类型,如MPI_INTEGER2,MPI_LONG_LONG_INT,等等。
表5.2 MPI原始数据类型
MPI系统的原始数据类型只适合于收发一组在内存中连续存放的数据。
当要收发的数据在内存中不连续,或由不同数据类型构成时,则需要将数据打包或者使用自定义的数据类型。
自定义数据类型用于描述要发送或接收的数据在内存中的确切分布。
数据类型是MPI的一个重要特征,它的使用可有效地减少消息传递的次数,增大通信粒度,并且,与数据打包相比,在收/发消息时可以避免或减少数据在内存中的拷贝、复制。
关于MPI自定义数据类型的详细使用,限于篇幅,在此不作详细说明,有兴趣的读者请参看参考文献文献[2]。
5.3 MPI程序的基本结构
一个MPI程序的各个进程通过调用MPI函数进行通信,协同完成一项计算任务。
在MPI的C语言接口中,所有函数名均采用MPI_Xxxxx的形式,如MPI_Send,MPI_Type_commit等等,它们以MPI_开始,以便与其他函数名相区别,前缀MPI_之后的第一个字母大写,其余字母一律小写。
MPI的C语言接口函数通常返回一个整数值表示操作成功与否,返回值为
MPI_SUCCESS (0) 表示操作成功,否则表示操作的错误码。
MPI接口中除了函数和SUBROUTINE外,还定义了一组常量及C变量类型,它们的命名规则为:所有常量的名称全部大写,如MPI_COMM_WORLD,MPI_INT等;而C变量类型的命名则与C函数一样,如MPI_Datatype,MPI_Status等。
MPI 并行程序和串行程序没有很大的差别,它们通过对MPI函数的调用来实现特定的并行算法。
一个MPI 并行程序主要由三个部分组成:
1 进入并行环境:调用MPI_Init来启动并行计算环境,它包括在指定的计算结点上启动构成并行程序的所有进程以及构建初始的MPI 通信环境和通信器MPI_COMM_WORLD、MPI_COMM_SELF。
2 主体并行任务:这是并行程序的实质部分,所有需要并行来完成的任务都在这里进行。
在这个部分中,实现并行算法在并行计算机上的执行过程。
3 退出并行环境:调用MPI_Finalize退出并行环境。
一般说来,退出并行计算环境后程序的运行亦马上结束。
下面是C语言MPI程序的典型结构
用C语言编写的MPI 程序中每个源文件必须包含MPI的C语言头文件mpi.h,以便得到MPI函数的原型说明及MPI预定义的常量和类型。
注意源文件中包含头文件“mpi.h”时不要含路径,必要时可在编译时通过“-I”选项指定mpi.h所在的路径,以方便程序在不同MPI 系统间的移植。
MPI_Init函数用于初始化MPI系统。
在调用其他MPI函数前(除MPI_Initialized外) 必须先调用该函数。
在许多MPI系统中,第一个进程通过MPI_Init来启动其他进程。
注意要将命令行参数的地址(指针) &argc和&argv传递给MPI_Init,因为MPI程序启动时一些初始参数是通过命令行传递给进程的,这些参数被添加在命令行参数表中,MPI_Init通过它们得到MPI程序运行的相关信息,如需要启动的进程数、使用那些结点、以及进程间的通信端口等,返回时会将这些附加参数从参数表中去掉。
因此一个MPI程序如果需要处理命令行参数,最好在调用MPI_Init之后再进行处理,这样可以避免遇到MPI系统附加的额外参数。
函数MPI_Comm_size与MPI_Comm_rank分别返回指定通信器(这里是MPI_COMM_WORLD,它包含了所有进程) 中进程的数目以及本进程的进程号。
MPI_Finalize函数用于退出MPI系统。
调用MPI_Finalize之后不能再调用任何其他MPI 函数。
各MPI函数的详细使用方法请参看下一节的例子程序及附录3,在此先不作详细说明。
5.4 MPI通信简介
5.4.1 点到点通信和通信模式
MPI最基本的通信模式是在一对进程之间进行的消息收发操作:一个进程发送消息,另一个进程接收消息。
这种通信方式称为点对点通信(point to point communications)。
MPI提供两大类型的点对点通信函数。
第一种类型称为阻塞型(blocking),第二种类型称为非阻塞型(non blocking)。
阻塞型函数需要等待指定操作的实际完成,或至少所涉及的数据已被MPI系统安全地备份后才返回。
如MPI_Send 和MPI_Recv都是阻塞型的。
MPI_Send 函数返回时表明数据已经发出或已被MPI系统复制,随后对发送缓冲区的修改不会影响所发送的数据。
而MPI_Recv返回时,则表明数据已经接收到并且可以立即使用。
阻塞型函数的操作是非局部的,它的完成可能需要与其他进程进行通信。
非阻塞型函数调用总是立即返回,而实际操作则由MPI系统在后台进行。
非阻塞型函数名MPI_前缀之后的第一个字母为“I”,最常用的非阻塞型点对点通信函数包括MPI_Isend 和MPI_Irecv。
在调用了一个非阻塞型通信函数后,用户必须随后调用其他函数,如MPI_Wait 或MPI_Test等,来等待操作完成或查询操作的完成情况。
在操作完成之前对相关数据区的操作是不安全的,因为随时可能与正在后台进行的通信发生冲突。
非阻塞型函数调用是局部的,因为它的返回不需要与其他进程进行通信。
在有些并行系统上,通过非阻塞型函数的使用可以实现计算与通信的重叠进行。
此外,对于点对点消息发送,MPI 提供四种发送模式,这四种发送模式的相应函数具有一样的调用参数,但它们发送消息的方式或对接收方的状态要求不同。
标准模式(standard mode)由MPI系统来决定是先将消息拷贝至一个缓冲区然后立即返回(此时消息的发送由MPI系统在后台进行),还是等待将数据发送出去后再返回。
大部分MPI系统预留了一定大小的缓冲区,当发送的消息长度小于缓冲区大小时会将消息拷贝到缓冲区然后立即返回,否则则当部分或全部消息发送完成后才返回。
标准模式发送操作是非局部的,因为它的完成需要与接收方联络。
标准模式阻塞型发送函数是MPI_Send。
缓冲模式(buffered mode)MPI系统将消息拷贝至一个用户提供的缓冲区然后立即返回,消息的发送由MPI系统在后台进行。
用户必须确保所提供的缓冲区足以容下采用缓冲模式发送的消息。
当消息大小超过缓冲区容量时,应用程序会收到错误汇报。
缓冲模式发送操作是局部的,因为函数不需要与接收方联络即可立即完成(返回)。
缓冲模式阻塞型发送函数为MPI_Bsend。
同步模式(synchronous mode)在标准模式的基础上要求确认接收方已经开始接收数据后函数调用才返回。
即发送动作的结束不仅意味着发送缓冲区已经可以用于其它用途,而且还表示接收端也执行了一定程序的接收工作。
显然,同步模式的发送是非局部的。
同步模式阻塞型发送函数为MPI_Ssend。
就绪模式(ready mode)调用就绪模式发送时必须确保接收方已经处于就绪状态(正在等待接收该消息),否则将产生一个错误。
该模式设立的目的是在一些以同步方式工作的并行系统上由于发送时可以假设接收方已经准备好接收而减少一些握手开销。
如果一个使用就绪模式的MPI 程序是正确的,则将其中所有就绪模式的消息发送改为标准模式后也应该是正确的。
就绪模式阻塞型发送函数为MPI_Rsend。
具体函数的使用请参看附录3。
各通信模式的详细分析和举例限于篇幅在此不作详细说明,有兴趣的读者请参看参考文献[2]。
5.4.2 聚合通信与同步
聚合通信指在一个通信器的所有进程间同时进行的通信。
聚合通信总是在一个通信器中的所有进程间进行,调用一个聚合通信函数时,通信器中的所有进程必须同时调用同一函数,共同参与操作。
聚合通信包括障碍同步(MPI_Barrier)、广播(MPI_Bcast)、数据收集(MPI_Gather)、数据散发(MPI_Scatter)、数据转置(MPI_Alltoall)和归约(MPI_Reduce)。
1. 障碍同步
障碍同步函数MPI_Barrier用于一个通信器中所有进程的同步。
调用该函数时进程将处于等待状态,直到通信器中所有进程都调用了该函数后才继续执行。
2. 广播
指一个进程(称为根进程) 同时发送同样的消息给通信器中的所有其他进程。
MPI 的广播函数是MPI_Bcast。
3. 数据收集
数据收集操作指一个进程,称为根进程,从指定通信器中的所有进程,包括根进程自己,收集数据。
MPI的基本数据收集函数为MPI_Gather,它从每个进程收集相同长度的数据。
如果从各个进程收集的数据长度不同,则应该调用函数MPI_Gatherv。
函数MPI_Allgather用于在通信器中的所有进程中同时进行数据收集,它的作用相当于先用MPI_Gather将数据收集到一个进程中,紧接着用MPI_Bcast将收集到的数据广播给其他进程。
类似地,MPI_Allgatherv用于收集不同长度的数据到通信器中的所有进程中。
4. 数据散发
数据散发函数MPI_Scatter正好是数据收集函数MPI_Gather的逆向操作,它将一个进程中的数据按块散发给通信器中的所有进程,散发给每个进程的数据块长度相同。
函数MPI_Scatterv用于散发不同长度的数据块。
5. 数据转置
函数MPI_Alltoall用于同时进行收集和散发操作:通信器中所有进程从其他进程收集数
据,同时将自己的数据散发给其他进程。
它的作用相当于将一个分布式存储的数据场在处理机间进行一次转置。
函数MPI_Alltoall 要求参与操作的所有数据块长度一样,如果数据块长度不同,则应该调用MPI_Alltoallv 函数。
6. 归约
归约运算是指在分布在不同进程的数据间进行指定的运算,常用的运算有求和、求最大或最小值等。
MPI 的归约函数中可以使用预定义的运算(如MPI_SUM ,MPI_MAX 等,参看附录3),也可以使用用户自行定义的运算(参看MPI_Op_create)。
MPI 用于归约操作的 基本函数是MPI_Reduce ,它在指定的进程(称为根进程) 中返回归约运算结果。
如果希望所有进程都得到归约运算的结果,则可使用函数MPI_Allreduce 。
此外,MPI 还提供一个函数MPI_Scan ,称为前缀归约或扫描归约,用于计算数据的部分和。
表5.4 MPI 点对点通信类型及模式汇总
5.5 一个经典的MPI 程序实例——数值积分(π值计算)
代码5.1 是取自MPICH 的一个程序实例,它展示了C 语言MPI 并行程序的结构。
该程序用下面的公式计算定积分(
)
x d x ⎰
+=
1
2
1/4π的近似值:
()∑-=1
0n i i x f h 式5.1
其中n>0为积分区间数。
n h /1=为积分步长。
()h i x i 5.0+= ()1,......,0-=n i 为积分区间的中点。
被积函数()()2
1/4x
x f +=。
假设总进程数为p (程序中的numprocs 变量),各进程分别负责计算式5.1中的一部分计算区间,然后再调用MPI_Reduce 将各进程的结果加起来。
代码中计算区间采用循环分配
的方式,即将计算公式写成:
()∑∑-==<≤1
mod ,0p k k
p j n j j
x f h
每个进程独立计算上式中的一个内层求和,然后再将这些结果加起来,如图5.1。
图5.3 π值计算分10小块时图示(10小块的面积相加近似于曲线f(x)发在0-1区间所围的面积)
代码5.1 MPI程序实例:数值积分(π值计算)。
文件名: code/mpi/cpi.c
程序详解:
第2行:#include "mpi.h"是预处理指令,用于包含mpi的头文件。
第5行:double f(double a){ return (4.0/(1.0+a*a));}定义被积函数f(x)。
第7行:int main( int argc, char *argv[])是主函数,包含了argc和argv参数,它们将被传入用于初始化MPI的函数。
第10行:double PI25DT = 3.141592653589793238462643定义一个比较精确的25位π值作为标准值,以分析本程序计算结果的误差。
第12行:double startwtime, endwtime定义变量开始时间startwtime和结束时间endwtime。
均为MPI_Wtime()的返回值。
第15行:MPI_Init(&argc,&argv)被每一个MPI进程调用的MPI函数都是MPI_Init。
该函数指示系统完成所有初始化工作,以备对后续MPI库的调用进行处理,因此,MPI_Init 要在调用任何MPI函数之前调用。
argc 和argv 分别是命令行参数的个数和参数数组的指针(通过C的main 函数得到),必须将它们如实传递给MPI 系统。
MPI 系统通过它们得到所需的参数,并且会将MPI 系统专用的参数删除而仅留下供用户程序使用的参数。
第16行:MPI_Comm_size(MPI_COMM_WORLD,&numprocs)当MPI初始化后,每一个活动进程变成一个叫做MPI_COMM_WORLD的默认通信域中的成员。
一个通信域是不透明的对象,提供了在进程之间传递消息的环境。
MPI_Comm_size(MPI_COMM_WORLD,&numprocs)用numprocs返回通信域MPI_COMM_WORLD中的进程数。
第17行:MPI_Comm_rank(MPI_COMM_WORLD,&myid)用myid返回通信域MPI_COMM_WORLD中本进程的进程号。
第18行:MPI_Get_processor_name(processor_name,&namelen) 该函数返回运行本进程的处理器名称。
参数processor_name应该提供不少于MPI_MAX_PROCESSOR_NAME 个字节的存储空间用于存放处理器名称。
第20-23行:是仅进程0执行的代码,给n赋值10000意味着将0-1的积分区间分成10000小块,对每一个小块计算面积。
同时,获取计算机开始的时间。
第24行:MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD)是广播函数。
MPI_Bcast(void *buffer, int count,MPI_Datatype datatype, int root,MPI_Comm comm)表示通信器comm 中进程号为root 的进程(称为根进程) 将自己buffer 中的内容发送给通信器中所有其他进程。
参数buffer、count 和datatype 的含义与点对点通信函数(如MPI_Send 和
MPI_Recv) 相同。
则MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD)表示通信器MPI_COMM_WORLD中进程号为0的进程将自己n 中的内容发送给通信器中所有其他进程。
第25-26行:为即将开始的计算做准备。
第27-30行:计算本进程所分配的各小块的高度和。
每一进程均从i= myid开始,每做一次计算往后跳numprocs块。
这样,每个进程均计算总小块数10000的一1/numprocs。
第31行:mypi = h * sum将每个进程所得的各小块的高度和与小块宽度相乘即得本进程所得的小块面积和即PI的部分值。
第32行:MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD)将各进程所得的PI的部分值进行归约。
&mypi为各个进程的归约元素的地址,&pi为归约计算(MPI_SUM)之后结果的存放地址,1为执行归约的次数,MPI_DOUBLE为元素类型,MPI_SUM为归约操作符(其它的操作符请参看附录3),0为得到结果的进程号,MPI_COMM_WORLD为通信域。
第33-37行:0号进程执行的代码,进程首先获取时间,再用现在的时候减去初始获取的时间即得到程序执行的时间并显示。
第38行:MPI_Finalize()在一个进程执行完其全部MPI库函数调用后,将调用函数MPI_Finalize,从而让系统释放分配给MPI的资源(例如内存等)。
所有进程均从前到后地执行本程序,各进程均为变量分配内存。
部分只有特定进程才执行的代码均放在条件语句中,一般用myid作为条件来决定本进程是否执行,如第20-23行。
每个进程都可有输出到屏幕操作,但其它进程的输出内容只会显示在根进程所在节点的屏幕上。
运行结果:
说明:
本MPICH安装在路径/usr/local下,文件cpi.c默认在/usr/local/mpich/examples下。
例子中为用4个节点运算的结果。
要编译程序,首先进入root用户,如第1-2行所示。
第3行编。