MPICH2在Windows环境下VisualStudio的环境搭建.docx
《MPICH2在Windows环境下VisualStudio的环境搭建.docx》由会员分享,可在线阅读,更多相关《MPICH2在Windows环境下VisualStudio的环境搭建.docx(19页珍藏版)》请在冰豆网上搜索。
MPICH2在Windows环境下VisualStudio的环境搭建
MPICH2在Windows系统VisualStudio2010的环境搭建
本机运行环境:
机器型号:
联想G450
处理器:
Pentium(R)Dual-CoreCPUT4200@2.00GHz
运行内存(RAM):
2GB
操作系统:
Windows7(32位)
程序开发环境:
VisualStudio2008
MPI版本:
mpich2-1.3.2p1-win-ia32安装程序
MPI的编译和运行
对于简单的程序,可以使用专门的编译命令。
对于大的项目,最好使用标准的Makefile。
MPICH提供的编译命令有mpicc和mpif77,它们分别是C和Fortran的编译命令:
mpicc-ofirstfirst.c
mpif77-ofirstfirstf.f
对于编译得到的目标程序,运行的命令为:
mpirun–archxxx–npyyyfirst
其中xxx为machines.的,yyy为申请的进程数目。
MPICH的安装和配置
文中使用的MPICH2安装文件是mpich2-1.4-win-ia32(http:
//www-unix.mcs.anl.gov/mpi/mpich),在Windows下安装MPICH2比较简单,但是要有Microsoft.NETFramework的支持。
安装基本上只要单击“Next”即可。
在安装过程中会提示输入进程管理器的密码,这个密码被用来访问所有的程序,这里使用的密码为admin。
如果是多台机器执行mpi,那么这多台机器上必须配置相同的mpi用户,就是新用户的用户名和密码必须相同。
安装完成后,安装目录下的include子目录包含了编程所需要的所有头文件,lib子目录包含了相应的程序库,而子目录bin则包含了MPI在Windows下面必须的运行程序。
运行时需要的动态链接库被安装在了Windows系统目录中。
在Windows平台下可以使用MicrosoftVisualStudio来开发MPI程序。
首先,下载mpich2,并进行安装。
本机的安装路径为:
C:
\ProgramFiles\MPICH2。
之后配置系统环境变量,计算机->属性->高级->环境变量->Path,如下图:
在系统中新创建一个用户,计算机->管理->右键用户->新用户,如下图:
运行wmpiregister.exe,配置mpi使用这个新创建的用户,点击填入用户名和密码,然后点击register,如下图:
用win32控制台应用程序新建一个MYMPI项目,将MPICH2安装目录下的include子目录加入到头文件目录中。
在MYMPI项目属性->配置属性->VC++目录对话框中添加include子目录,如下图:
再用相同的方法将MPICH2\lib加入到库文件目录中,如下图:
同样方法,配置程序,连接mpi库,如下图:
为了避免名字冲突,需要在预编译头文件stdafx.h中加入#inlcudempi.h语句。
现在就可以在主程序文件中编写MPI程序了,MPI的开发环境配置完毕。
在Windows下运行MPI程序
按照上面配置好开发环境之后,在MYMPI.CPP文件中输入下面的程序。
在项目属性的“配置属性”->“常规”项中的“字符集”设置为“未设置”。
程序参数说明
一般的MPI程序设计流程图:
进入MPI系统,通信器MPI_COMM_WORLD形成
CallMPIInit()
CallMPI_Comm_rank()
CallMPI_Comm_size()
建立新的通信器、定义新的数据类型和进程拓扑结构
应用程序实体:
1.计算控制程序体;
2.进程间通信;
退出MPI系统
CallMPI_Finalize()
End
然后编写测试程序:
#include "stdafx.h"
#include "mpi.h"
#include
using std:
:
cout;
using std:
:
endl;
int_tmain(intargc,_TCHAR*argv[])
{intrank,size;
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&size);
printf("HelloWorldfromthread%dof%d\n",rank,size);
MPI_Finalize();
return0;
}
这个程序比较简单,在函数MPI_Init()和MPI_Finalize()之间是程序并行执行的地方,MPI_Init()、MPI_Comm_rank()、MPI_Comm_size()和MPI_Finalize(),这四个函数是MPI中最重要和最常用的函数。
下面分别说明:
(1)MPI_Init和MPI_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,按照并行执行的方式,上面程序运行结果应该打印两行文字信息,为:
HelloWorldfromthread0of2
HelloWorldfromthread1of2
编译运行这个程序,如下图:
:
本机系统环境变量OMP_NUM_THREADS值是2,但是运行结果确只打印了一行,显然函数MPI_Init和MPI_Finalize之间的代码仅被一个线程串行执行了。
MPI程序若要被正确运行,需要使用MPICH2安装目录下的运行工具MPIEXECwrapper运行用VS2005生成的exe文件。
启动这个程序,程序的界面如图
在“Application”栏中选择要运行的exe程序,在“Numberofprocess”栏中选择要运行程序的线程数,然后单击“Execute”按钮运行程序。
运行上面的示例程序,输出结果如图所示
4线程分别执行MPI_Init和MPI_Finalize之间的代码,打印4行信息,程序执行结果正确。
MPI的点对点通信
有消息传递功能的并行程序,消息传递是MPI编程的核心功能,掌握了MPI消息传递编程方法就掌握了MPI编程的核心。
点对点通信是MPI程序的基础,MPI_Send和MPI_Recv是两个最重要的函数。
这两个函数的标准形式是:
intMPI_Send(buf,counter,datatype,dest,tag,comm)
参数作用如下:
buf:
发送缓冲区的起始地址,可以是数组或结构指针
count:
非负整数,发送的数据个数
datatype:
发送数据的数据类型
dest:
整型,目的的进程号
tag:
整型,消息标志
comm:
MPI进程组所在的通信域
这个函数返回整型的错误码,它的含义是向通信域中的dest进程发送数据,数据存放在buf中,类型是datatype,个数是count,这个消息的标志是tag,用以和本进程向同一目的进程发送的其它消息区别开来。
intMPI_Recv(buf,count,datatype,source,tag,comm,status)
参数作用如下:
buf:
接收缓冲区的起始地址,可以是数组或结构指针
count:
非负整数,最多可接收的数据个数
datatype:
接收数据的数据类型
source:
整型,接收数据的来源,即发送数据进程的进程号
tag:
整型,消息标识,应与发送操作的消息标识相同
comm:
消息接收进程所在的通信域
status:
MPI_Status结构指针,返回状态信息
这个函数返回整型的错误码,它的含义是进程从comm域中source进程接收标签号为tag的数据,并保存到buf中。
接收缓冲区buf的大小不能小于发送过来的消息的长度。
否则会由于数组越界导致程序出错。
参数status是MPI_Status类型的,status主要显示接收函数的各种错误状态。
通过访问status.MPI_SOURCE、status.MPI_TAG和status.MPI_ERROR就可以得到发送数据的进程号、使用的标签以及接收操作的错误代码。
另外,还可以使用函数MPI_Get_count来获得实际接收到的数据项数。
MPI_Get_count的标准定义为:
intMPI_Get_count(MPI_Status*status,MPI_Datatypedatatype,int*count);将实际接收到数据项数存放到count中。
下面用一个程序说明上面提到的函数的使用方法。
函数MPI_Get_processor_name用于获得计算机名,并存放在processor_name中,长度为namelen,宏定义MPI_MAX_PROCESSOR_NAME是机器名的最大长度。
这个程序的完成的任务是使进程i发送数据给进程i+1,并等待由进程i-1发送来的数据。
最后一个进程则发送数据给进程0。
群体通信
群体通信意味着一个通信子中的所有进程调用同一例程,所有的群体操作都是阻塞的,它包括如下一些:
●同步(barrier)
●从一个进程到本组内的所有进程的播送(broadcast)(如图3.3的(a))
●从本组所有处理收集数据到一个进程(gather)(如图3.3的(b))
●从一个进程分散数据到本组内的所有进程(sactter)(如图3.3的(b))
●将gather的数据不是送到某一进程,而是要送到所有本组内的进程(allgather)(如图3.3的(c))
●组内的多对多的分散/收集(alltoall)(如图3.3的(d))
●求和,最大值,最小值及用户定义的函数等的reduce操作
●scan或prefix操作
图3.3群体操作的图例
1、同步
intMPI_Barrier(MPI_Commcomm)
它使得调用者阻塞,直到该通信子内所有进程都调用它。
2、广播
intMPI_Bcast(void*buffer,intcount,MPI_Datatypedatatype,introot,
MPI_Commcomm);
所有进程使用同一计数、数据类型、根和通信子。
在操作前,根缓冲区包含一个消息。
操作后,所有缓冲区包含来自根进程的消息。
3、散播
intMPI_Scatter(void*sndbuf,intsndcnt,MPI_Datatypesndtype,void*rcvbuf,
intrcvcnt,MPI_Datatypercvtype,introot,MPI_Commcomm);
所有进程使用同一计数、数据类型、根和通信子。
在操作前,根发送缓冲区包含长度为`sndcnt*N'的消息,这里N是进程数目。
操作后,相等地划分消息,并且分散到随后标识数序的所有进程(包括根)。
4、归约
intMPI_Reduce(void*sndbuf,void*rcvbuf,intcount,MPI_Datatypedatatype,
MPI_Opop,introot,MPI_Commcomm);
所有进程使用同一计数、数据类型、根和通信子。
操作后,根进程在它的接受缓冲区中有所有进程的发送缓冲区的归约结果,包括:
MPI_MAX,MPI_MIN,MPI_SUM,MPI_PROD,MPI_LAND,MPI_BAND,MPI_LOR,MPI_BOR,MPI_LXOR,MPI_BXOR,或者是用户定义的归约函数。
5、收集
intMPI_Gather(void*sndbuf,intsndcnt,MPI_Datatypesndtype,void*rcvbuf,
intrcvcnt,MPI_Datatypercvtype,introot,MPI_Commcomm);
所有进程使用同一计数、数据类型、根和通信子。
此例程是MPI_Scatter()的相反:
操作后,根进程在它的接受缓冲区中包含所有进程的发送缓冲区的连接(包括它自己),所有消息长度为`rcvcnt*N',这里N是进程数目。
按照随后的标识数序收集消息。
6、群体通信的例子
下面简单的代码段使用了四个基本的集合例程以操纵一个静态的已划分的规则区域(这里是一维)。
全域的长度从根进程广播到所有其它进程。
初始数据集在进程间分配(分散)。
在每一计算步骤之后,确定全局的最大数并由根所使用。
根然后收集最终的数据集。
#include
{
inti,myrank,size,root,full_domain_length,sub_domain_length;
doubleglobal_max,local_max,*full_domain,*sub_domain;
MPI_Comm_rank(MPI_COMM_WORLD,&myrank);
MPI_Comm_size(MPI_COMM_WORLD,&size);
root=0;
if(myrank==root)get_full_domain(&full_domain,&full_domain_length);
MPI_Bcast(&full_domain_length,1,MPI_INT,root,MPI_COMM_WORLD);
sub_domain_length=full_domain_length/size;
sub_domain=(double*)malloc(sub_domain_length*sizeof(double));
MPI_Scatter(full_domain,sub_domain_length,MPI_DOUBLE,sub_domain,
sub_domain_length,MPI_DOUBLE,root,MPI_COMM_WORLD);
compute(sub_domain,sub_domain_length,&local_max);
MPI_Reduce(&local_max,&global_max,1,MPI_DOUBLE,MPI_MAX,
root,MPI_COMM_WORLD);
MPI_Gather(sub_domain,sub_domain_length,MPI_DOUBLE,full_domain,
sub_domain_length,MPI_DOUBLE,root,MPI_COMM_WORLD);
}
参考第四讲MPI编程及矩阵相乘。
Ppt
统计时间函数
为了验证程序并行化后的效果,MPI提供了两个用于统计时间的函数MPI_Wtime和MPI_Wtick。
其中MPI_Wtime返回一个双精度数,表示从过去某点的时刻到当前时刻所消耗的时间秒数。
而函数MPI_Wtick则返回MPI_Wtime结果的精度。
修改例3_2程序,在并行代码两端加入统计时间的函数,如例3_3:
例3_3
begin=MPI_Wtime();
end=MPI_Wtime();
diff=end-begin;
printf("%dprocesstimeis%9.7f\n",myid,diff);
printf("%dprocesstickis%9.7f\n",myid,MPI_Wtick());
运行结果如图3-9:
图3-9例3_3的运行结果
开发实例
下面将在Windows平台上使用MPI编写一个用数值积分法计算圆周率的程序。
利用公式PI=
的近似值计算圆周率[7],定积分的计算可以转化为求一个曲边梯形的面积问题。
将积分区间等分成n个小的子区间,可将每个小的子区间上的曲边梯形近似地看成矩形,这些矩形面积的和就近似地等于原来曲边梯形的面积。
这样最终将求圆周率的问题转化成了一个面积迭加的计算。
每个小矩形的宽为
(n为将积分区间等分的份数),高可以将x值带入函数
求得。
用循环将每个小矩形的面积累加起来便是PI的近似值。
具体的算法实现见附加中的程序“mpi_pi”。
图3-10、3-11分别是用一个进程和两个进程运行的结果。
图3-10使用一个进程的运行结果
图3-11使用两个进程的运行结果
从运行结果可以看到使用两个进程时的计算速度反而不如用一个进程运行时的速度,这时由于本程序的计算规模不大,另外引入一个进程的开销大于程序并行所带来的益处,所以进程数越多反而程序的运行速度越慢。
看下面一组数据[8](表3-1)
这组数据是在不同的硬件平台下实现本开发实例程序的计算时间。
运行环境为3台计算机组成的集群,配置均为CPU:
IntelPentiumIII733MHz,相同的算法,随着参与计算的机器数增加,计算时间减少。
MPI是针对分布式计算机系统提出的,它采用非共享内存的方式利用多进程完成并行任务,当计算规模不大或处理器数量不多时,更多进程的维护会增加系统的开销,而且进程之间的通信存在延时。
它比较适合集群计算机系统。
计算机数
计算时间
1
1.63643
2
0.83180
3
0.55622
表3-1