MPI
说到并行计算,我们有一个不可绕开的话题——MPI编程。MPI是一个跨语言的通讯协议,用于编写并行计算机。支持点对点和广播。MPI是一个信息传递应用程序接口,包括协议和和语义说明,他们指明其如何在各种实现中发挥其特性。MPI的目标是高性能,大规模性,和可移植性。MPI在今天仍为高性能计算的主要模型。与OpenMP并行程序不同,MPI是一种基于信息传递的并行编程技术。消息传递接口是一种编程接口标准,而不是一种具体的编程语言。简而言之,MPI标准定义了一组具有可移植性的编程接口。
什么是MPI
对MPI的定义是多种多样的, 但不外乎下面三个方面, 它们限定了MPI的内涵和外延。
MPI是一个库 而不是一门语言 。 MPI库可以被FORTRAN77/C/Fortran90/C++调用 从语法上说,它遵守所有对库函数/过程的调用规则,和一般的函数/过程没有什么区别。
MPI是一种标准或规范的代表, 而不特指某一个对它的具体实现。 迄今为止 ,所有的并行计算机制造商都提供对MPI的支持, 可以在网上免费得到MPI在不同并行计算机上的实现, 一个正确的MPI程序 ,可以不加修改地在所有的并行机上运行。
MPI是一种消息传递编程模型, 并成为这种编程模型的代表和事实上的标准。 MPI虽然很庞大, 但是它的最终目的是服务于进程间通信这一目标的。
在MPI上很容易移植其它的并行代码, 而且编程者不需要去努力掌握许多其它的全新概念, 就可以学习编写MPI程序, 当然 这并不意味着MPI已经十分完美, 必须承认MPI自身还存在着一些缺点。
消息传递方式是广泛应用于多类并行机的一种模式,特别是那些分布存储并行机 , 尽管在具体的实现上有许多不同, 但通过消息完成进程通信的基本概念是容易理解的。 十多年来, 这种模式在重要的计算应用中已取得了实质进步 。有效和可移植地实现一个消息传递系统是可行的, 因此, 通过定义核心库程序的语法, 语义, 这将在大范围计算机上可有效实现,将有益于广大用户 ,这是MPI产生的重要原因 。
MPI的目的
MPI为自己制定了一个雄心勃勃的目标 ,总结概括起来 ,它包括几个在实际使用中都十分重要但有时又是相互矛盾的三个方面 :1 较高的通信性能 ;2 较好的程序可移植性; 3 强大的功能 。具体地说 包括以下几个方面:
- 提供应用程序编程接口
- 提高通信效率, 措施包括避免存储器到存储器的多次重复拷贝, 允许计算和通信的重叠等
- 可在异构环境下提供实现
- 提供的接口可以方便 C 语言和 Fortran 77的调用
- 提供可靠的通信接口 ,即用户不必处理通信失败
- 定义的接口和现在已有接口 ,如(PVM NX Express p4等) 差别不能太大, 但是允许扩展以提供更大的灵活性
- 定义的接口能在基本的通信和系统软件无重大改变时, 在许多并行计算机生产商的平台上实现。 接口的语义是独立于语言的
- 接口设计应是线程安全的
MPI提供了一种与语言和平台无关, 可以被广泛使用的编写消息传递程序的标准 ,用它来编写消息传递程序 ,不仅实用 ,可移植 ,高效和灵活, 而且和当前已有的实现没有太大的变化 。
名词和概念
程序代码:
这里的程序不是指以文件形式存在的源代码、可执行代码等,而是指为了完成一个计算任务而进行的一次运行过程。
进程(Process)
一个 MPI 并行程序由一组运行在相同或不同计算机 /计算节点上的进程或线程构成。为统一起见,我们将 MPI 程序中一个独立参与通信的个体称为一个进程。
进程组:
一个 MPI程序的全部进程集合的一个有序子集。进程组中每个进程都被赋予一个在改组中唯一的序号(rank),用于在该组中标识该进程。序号范围从 0 到进程数-1。
通信器(communicator):
有时也译成通信子,是完成进程间通信的基本环境,它描述了一组可以互相通信的进程以及它们之间的联接关系等信息。MPI所有通信必须在某个通信器中进行。通信器分域内通信器(intracommunicator)和域间通信器(intercommunicator)两类,前者用于同一进程中进程间的通信,后者则用于分属不同进程的进程间的通信。
MPI 系统在一个 MPI 程序运行时会自动创建两个通信器:一个称为 MPI_COMM_WORLD,它包含 MPI 程序中所有进程,另一个称为MPI_COMM_SELF,它指单个进程自己所构成的通信器。
序号(rank):
即进程的标识,是用来在一个进程组或一个通信器中标识一个进程。MPI 的进程由进程组/序号或通信器/序号唯一确定。
消息(message):
MPI 程序中在进程间传递的数据。它由通信器、源地址、目的地址、消息标签和数据构成。
通信(communication):
通信是指在进程之间进行消息的收发、同步等操作。
MPI核心接口
用过Hadoop的童鞋应该都记得经典的Map和Reduce接口,我们在写MR程序的时候主要就在写自己实现的Map和Reduce方法。
MPI比Hadoop需要关注的稍微多一点点。
注意: 这几个核心的接口还是要了解一下的。暂时可以看一眼跳过去,后面在看程序的时候回过头多对比一下就能记住了。
我们简单地理解一下这6个接口,其实可以分为3类:
- 开始和结束MPI的接口:
MPI_Init、MPI_Finalize - 获取进程状态的接口:
MPI_Comm_rank、MPI_Comm_size - 传输数据的接口:
MPI_Send、MPI_Recv
1. MPI_Init(&argc, &argv)
初始化MPI执行环境,建立多个MPI进程之间的联系,为后续通信做准备。
2. MPI_Comm_rank(communicator, &myid)
用来标识各个MPI进程的,给出调用该函数的进程的进程号,返回整型的错误值。两个参数:MPI_Comm类型的通信域,标识参与计算的MPI进程组; &rank返回调用进程中的标识号。
3. MPI_Comm_size(communicator, &numprocs)
用来标识相应进程组中有多少个进程。
4. MPI_Finalize()
结束MPI执行环境。
5. MPI_Send(buf,counter,datatype,dest,tag,comm)
- buf:发送缓冲区的起始地址,可以是数组或结构指针;
- count:非负整数,发送的数据个数;
- datatype:发送数据的数据类型;
- dest:整型,目的的进程号;
- tag:整型,消息标志;comm:MPI进程组所在的通信域
含义:向通信域中的dest进程发送数据,数据存放在buf中,类型是datatype,个数是count,这个消息的标志是tag,用以和本进程向同一目的进程发送的其它消息区别开来。
6. MPI_Recv(buf,count,datatype,source,tag,comm,status)
- source:整型,接收数据的来源,即发送数据进程的进程号;
- status:MPI_Status结构指针,返回状态信息。
MPI调用借口的总数虽然庞大, 但根据实际编写MPI的经验, 常用的MPI调用的个数确什么有限。 下面是6个最基本的MPI函数。 1. MPI_Init(…); 2. MPI_Comm_size(…); 3. MPI_Comm_rank(…); 4. MPI_Send(…); 5. MPI_Recv(…); 6. MPI_Finalize(); 我们在此通过一个简单的例子来说明这6个MPI函数的基本用处。
重要说明
1.MPI标识一条消息的信息包含四个域:
- 源:发送进程隐式确定,进程rank值唯一标识.
- 目的:Send函数参数确定.
- Tag:Send函数参数确定, (0,UB) 232-1.
- 通信子:缺省MPI_COMM_WORLD
- Group:有限/N, 有序/Rank [0,1,2,…N-1]
- Contex:Super_tag,用于标识该通讯空间.
2. buffer的使用
- buffer必须至少可以容纳count个由datatype指明类型的数据. 如果接收buf太小, 将导致溢出、 出错
3. 消息匹配
- – 参数匹配source,tag,comm/dest,tag,comm.
- – Source == MPI_ANY_SOURCE: 接收任意处理器来的数据(任意消息来源).
- – Tag == MPI_ANY_TAG: 匹配任意tag值的消息(任意tag消息).