第三章 MPI编程
3.1 MPI简介
多线程是一种便捷的模型,其中每个线程都可以访问其它线程的存储空间。因此,这种模型只能在共享存储系统之间移植。一般来讲,并行机不一定在各处理器之间共享存储,当面向非共享存储系统开发并行程序时,程序的各部分之间通过来回传递消息的方式通信。要使得消息传递方式可移植,就需要采用标准的消息传递库。这就促成的消息传递接口(Message Passing Interface, MPI)的面世,MPI是一种被广泛采用的消息传递标准[1]
OpenMP并行程序不同,MPI是一种基于消息传递的并行编程技术。消息传递接口是一种编程接口标准,而不是一种具体的编程语言。简而言之,MPI标准定义了一组具有可移植性的编程接口。各个厂商或组织遵循这些标准实现自己的MPI软件包,典型的实现包括开放源代码的MPICHLAM MPI以及不开放源代码的Intel MPI。由于MPI提供了统一的编程接口,程序员只需要设计好并行算法,使用相应的MPI库就可以实现基于消息传递的并行计算。MPI支持多种操作系统,包括大多数的类UNIXWindows系统。
3.1.1如何实现MPI
MPI是一个标准。它不属于任何一个厂商,不依赖于某个操作系统,也不是一种并行编程语言。不同的厂商和组织遵循着这个标准推出各自的实现,而不同的实现也会有其不同的特点。MPICH是影响最大、用户最多的MPI实现。目前可下载的最新的MPICH软件包为MPICH 1.2.7pl2008215日发布的MPICH 2-1.0.7测试版(我使用的是MPICH 2-1.0.6pl),在v/research/projects/mpich2/index.php可以下载到,分别有支持UNIXWindows32位和64位版本。
3.1.2 MPI程序的特点
MPI程序是基于消息传递的并行程序。消息传递指的是并行执行的各个进程具有自己独立的堆栈和代码段,作为互不相关的多个程序独立执行,进程之间的信息交互完全通过显示地调用通信函数来完成。
3.2 MPICH的安装和配置
我使用的MPICH2安装文件是mpich2-1.0.6p1-win32-ia32.msiWindows下安装MPICH2比较简单,但是要有Microsoft .NET Framework 2.0的支持。安装基本上只要单击“Next”即可。
在安装过程中会提示输入进程管理器的密码,这个密码被用来访问所有的程序,这里使用的密码为admin
安装完成后,安装目录下的include子目录包含了编程所需要的所有头文件,lib子目录包含了相应的程序库,而子目录bin则包含了MPIWindows下面必须的运行程序。运行时需要的动态链接库被安装在了Windows系统目录中。在Windows平台下可以使用Microsoft Visual Studio来开发MPI程序,下面举例说明。
首先,新建一个Win32控制台项目,然后将MPICH2安装目录下的include
 
图3-1 配置头文件目录
 
子目录加入到头文件目录中。在VS 2005的菜单 工具->选项->项目解决方案->VC++目录对话框中添加include子目录,如图3-1所示。再用相同的方法将MPICH2\lib加入到库文件目录中,如图3-2。
 
图3-2 配置库文件目录
 
为了避免名字冲突,需要在预编译头文件stdafx.h中加入#inlcude mpi.h语句。现在就可以在主程序文件中编写MPI程序了,MPI的开发环境配置完毕。
3.3 在Windows下如何运行MPI程序
我所进行的MPI程序的开发均是在Windows平台下,使用Visual Studio 2005 + MPIEXEC wrapper 进行的,首先用一个简单的Hello World 程序说明运行环境的配置。
按照上一小节介绍配置好开发环境之后,在VS 2005中新建立一个Win32 控制台项目,并取名MPI1,在MPI1.CPP文件中输入下面的程序。在项目属性的“配置属性”->“常规”项中的“字符集”设置为“未设置”,如图3-3所示。
例3_1
int _tmain(int argc, _TCHAR* argv[])
int rank, size;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    printf("Hello World from thread %d of %d\n", rank, size);
    MPI_Finalize();
    return 0;
}
这个程序比较简单,在函数MPI_Init()MPI_Finalize()之间是程序并行执行的地方,MPI_Init()MPI_Comm_rank()MPI_Comm_size()MPI_Finalize()这四个函数是MPI中最重要和最常用的函数。下面分别说明:
 
图3-3 配置项目属性
(1)    MPI_InitMPI_Finalize
MPI_Init用来初始化MPI执行环境,建立多个MPI进程之间的联系,为后续通信做准备。而MPI_Finalize则是结束MPI执行环境。这两个函数就是定义MPI程序的并行区的,除了检测是否初始化的函数之外,不应该在这两个函数定义的区域外调用其它MPI函数。这两个函数都
返回整型值,标识函数是否调用成功。
(2)    MPI_Comm_rank
MPI_Comm_rank函数就是用来标识各个MPI进程的,给出调用该函数的进程的进程号。MPI_Comm_rank返回整型的错误值,需要提供两个参数:
        MPI_Comm类型的通信域,标识参与计算的MPI进程组。上面例子中使用的是MPI_COMM_WORLD,这个进程组是MPI实现预先定义好的进程组,指的是所有MPI进程所在的进程组。如果想要申请自己的特殊的进程组,则需要通过MPI_Comm定义并通过其它MPI函数生成。
        &rank返回调用进程中的标识号。
MPI还定义了另一个进程组MPI_COMM_SELF,只包含各个进程自己的进程组。
(3)    MPI_Comm_size
这个函数则用来标识相应进程组中有多少个进程,它也有两个参数:
        MPI_Comm类型的通信域,标识参与计算的MPI进程组。上面的例子中用的是MPI_COMM_WORLD
        &size返回相应进程组中的进程数。
运行这个程序,运行结果如图3-4,按照并行执行的方式,上面程序运行结果应该打印两行文字信息,为:
Hello World from thread 0 of 2
Hello World from thread 1 of 2
 
 
图 3-4 例3_1在windows上的运行结果
(本机系统环境变量OMP_NUM_THREADS值是2),但是运行结果确只打印了一行,显然函数MPI_InitMPI_Finalize之间的代码仅被一个线程串行执行了。经过查询资料知道,MPI程序若要被正确运行需要使用MPICH2安装目录下的运行工具MPIEXEC wrapper运行用VS 2005生成的exe文件。启动这个程序,程序的界面如图3-5
 
 图 3-5 MPIEXEC wrapper程序界面
由于该程序只有操作系统的管理员才有权使用,所以在第一次运行时需要输入计算机用户名和口令,并且不允许口令为空,如图3-6。输入完毕后,单击“Register”按钮完成注册,之后就可以使用该工具运行MPI程序了。
“Application”栏中选择要运行的exe程序,在“Number of process”栏中选择要运行程序的线程数,然后单击“Execute”按钮运行程序。如用4线程运行上面的示例程序,输出结果如图3-7所示。
 
            图 3-6 输入系统用户名和口令
 
图 3-7 使用MPIEXEC wrapper运行例3_1的结果
 
4线程分别执行MPI_InitMPI_Finalize之间的代码,打印4行信息,程序执行结果正确。
3.4 MPI的点对点通信
点对点通信是MPI程序的基础,MPI_SendMPI_Recv是两个最重要的函数。这两个函数的标准形式是:
        int MPI_Send(buf, counter, datatype, dest, tag, comm)
参数作用如下:
    buf发送缓冲区的起始地址,可以是数组或结构指针
    count非负整数,发送的数据个数
    datatype发送数据的数据类型
    dest整型,目的的进程号
    tag整型,消息标志
    commMPI进程组所在的通信域
这个函数返回整型的错误码,它的含义是向通信域中的dest进程发送数据,数据存放在buf中,
类型是datatype,个数是count,这个消息的标志是tag,用以和本进程向同一目的进程发送的其它消息区别开来。
        int MPI_Recv(buf, count, datatype, source, tag, comm, status)
参数作用如下:
  buf接收缓冲区的起始地址,可以是数组或结构指针
  count非负整数,最多可接收的数据个数
    datatype接收数据的数据类型
    source整型,接收数据的来源,即发送数据进程的进程号
    tag整型,消息标识,应与发送操作的消息标识相同
    comm消息接收进程所在的通信域
    statusMPI_Status结构指针,返回状态信息
进程间通信 共享内存
这个函数返回整型的错误码,它的含义是进程从comm域中source进程接收标签号为tag的数据,并保存到buf中。接收缓冲区buf的大小不能小于发送过来的消息的长度。否则会由于数组越界导致程序出错。参数statusMPI_Status类型的,status主要显示接收函数的各种错误状态。通过访问status.MPI_SOURCEstatus.MPI_TAGstatus.MPI_ERROR就可以得到发送数据的进程号、使用的标签以及接收操作的错误代码。另外,还可以使用函数MPI_Get_count来获得实际接收到的数据项数。MPI_Get_count的标准定义为:int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count);将实际接收到数据项数存放到count中。下面用一个程序说明上面提到的函数的使用方法。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。