0%

并行编程 MPI初探

刚刚跟实验室的一个博士生师兄联系上了,研究生入学前本来漫无目的的乱学过程终于稍微找到点方向。虽然还没决定以后具体要做体系结构方向的哪一块内容,至少先走一步学一步吧。

师兄是主要做并行程序优化的,估计老师也是看我以前编程方面还可以才给我推荐的这个方向。

下一阶段开始学习并行编程:mpi和openmp

先从mpi开始吧。

一些资料

MPICH在Windows下的安装

MPICH官网上提供了整个MPICH2的源码,一般是需要下过来然后在本地进行完整编译,这个等下次移到Ubuntu下面的时候再看一下编译的全过程吧。现在先找现成的安装包用着先。

最新的版本大概出到了3.x,不过已经编译好了的Windows安装文件似乎最高只到1.4.1。下之,然后安装。


安装过程中要求输入一串smpd的底层密码,这个应该是在整个MPICH系统中用来传递消息的一个服务。

装好之后,安装目录里面有用的几个exe有:

mpiexec.exe MPI运行器
smpd.exe
wmpiconfig.exe 图形界面下的MPICH配置查看器
wmpiexec.exe 图形界面下的MPI运行器
wmpiregister.exe 图形界面下的MPICH服务注册器


首先还是把bin这个目录加到系统环境变量里面去,这是一般装开发环境时肯定要做的事。

然后测试smpd服务是否在运行了,没有的话就需要把它安装上并启动起来:

1
smpd -install -phrase behappy

-phrase后面的这一串就是安装过程中输入的密码串。

确认已启动后继续。


启动wmpiregister.exe,这里需要把此时登录Windows系统的用户账户和密码输进去,因为MPICH需要调用管理员的底层权限。

比较坑的是当时在这里以及后面的测试这一步中卡了很久…

我现在用的是win10,从win8开始就使用了在线的微软账号进行登录。所以我把微软账号输入到register里面去,结果却死活没办法通过系统的验证。

后来才知道,如果采用微软账号登录系统,Windows在本地是还有另一个本地账号的用户名来对应的。一般是把登录微软账号前的最后一个本地账号直接作为对应。

我当时装机的时候应该是没有经过本地账号这一步,所以不知道它写在那里的本地用户名是什么。

于是切到本地账户,创建了一个本地账户的用户名,再切回微软账号。

然后用之前的本地账户用户名+微软账号的密码注册到register里面去。


注册完之后,可以用wmpiexec.exe测试一下,安装目录下example文件夹中有一个测试用的圆周率计算程序cpi.exe。

测试成功能运行即可。

不成功的话就需要检查smpd服务是不是正常运行,然后网络有没有被防火墙挡了,用户账户有没有注册到register里面去。

编译

网上提到MPI编译需要用到mpigcc之类的东西,然而我在MPICH2的安装目录下根本找不到这玩意的迹象。

官方文档中建议使用Visual Studio来作为MPI的IDE…然而,这货这么大,我是真心不太想用的感觉(尤其是VC6.0给我留下的印象太差了,不了解现在新版的VS套件的编译标准是不是跟GNU的一样)…

再仔细搜索之后,原来mpigcc就是调用了gcc的编译指令,这样事情就简单了。

看一下安装目录下面的文档,果然找到了gcc的说明:
README.winbin.rtf

1
2
3
4
5
6
7
8
9
...
For gcc/g77
1) create a makefile
2) add –I…mpich2\include
3) add –L…mpich2\lib
4) add –lmpi (for g77: -lfmpich2g)
5) add the rules for your source files
6) same as 6,7,8 above
...

整个过程很简单,只要在gcc的编译指令后面加上-I开关,把mpi的头文件include包括进来;-L开关,把mpi的函数库lib包括进来;最后指定-lmpi,使用mpi方式进行编译即可。


这样直接调用gcc和g++就可以对整个完成编译了,只不过运行必须调用mpiexec或者wmpiexec。

既然gcc的编译这一部分OK了,可以直接把编译命令绑进文本编辑神器Sublime2里面去。

Sublime2配置

配置部分跟以前已经记录过的一样:Sublime 2 配置

在sublime2中找到编译命令的配置文件:

Preference -> Bowser Packages -> /C++/C++.sublime-build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"cmd": ["g++", "${file}", "-o", "${file_base_name}"],
"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
"working_dir": "${file_path}",
"selector": "source.c, source.c++",
"encoding": "gbk",
"variants":
[
{
"name": "Run",
"cmd": ["${file_base_name}"]
//"cmd": ["cmd", "/c", "g++", "${file}", "-o", "${file_path}/${file_base_name}", "&&", "cmd", "/c", "${file_path}/${file_base_name}"]
},
{
"name": "RunInCommand",
"cmd": ["cmd", "/c", "start", "cmd", "/c", "${file_base_name} & pause"]
},
{
"name": "BuildAndRun",
"cmd": ["cmd", "/c", "g++", "${file}", "-o", "${file_base_name}", "&&", "start", "cmd", "/c", "${file_base_name} & pause"]
},
{
"name": "BuildWithMPI",
"cmd": ["g++", "${file}", "-o", "${file_base_name}", "-I", "D:\\Program Files\\MPICH2\\include", "-L", "D:\\Program Files\\MPICH2\\lib", "-lmpi"]
}
]
}

前面部分保持不变,最后加上一条命令,命名成BuildWithMPI

然后选一个没有被使用过的快捷键,加到快捷键注册文件里面去:

Preference -> Key Bindings – User

1
2
3
4
[
{ "keys": ["f10"], "command": "build", "args": {"variant": "RunInCommand"} },
{ "keys": ["alt+b"], "command": "build", "args": {"variant": "BuildWithMPI"} }
]

保存好,之后就可以用alt+b来调用附加了MPI编译命令的g++指令了。

Hello World~!

写个测试程序试试先:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/* ***********************************************
MYID : Chen Fan
LANG : G++
PROG : mpitest
************************************************ */

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <mpi.h>
#include <ctime>

using namespace std;

int main()
{
printf("test start:\n");

int test=0;
time_t t;
t= time(NULL);
printf("start time:%ld\n",t);

//MPI环境初始化
MPI_Init(NULL, NULL);

//获取进程数量
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);

//获取进程号
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

//获取处理器名
char processor_name[MPI_MAX_PROCESSOR_NAME];
int name_len;
MPI_Get_processor_name(processor_name, &name_len);

//Hello world!
printf("Hello world from processor %s, rank %d out of %d processors\n",processor_name,world_rank,world_size);

test++;
printf("%d\n",test);
t= time(NULL);
printf("end time:%ld\n",t);
printf("\n");

//关闭MPI环境
MPI_Finalize();

return 0;
}

此时普通的编译指令会返回找不到头文件mpi.h,就算找到了也是编译失败。
alt+b可正常通过编译。

然后用wmpiexec来运行之,开了4个线程:

结果:


MPI我还没有真正开始学,不过也可以从这个简单的测试程序中稍微看出一些特点来:

  • 从test这个变量的输出结果来看,4个线程的变量应该是完全独立的,并没有出现同一个变量反复加了4次这种事情。

  • 我还特别调用了一下时间函数。从输出结果上来看,四个进程的时间居然真的是同步的!!结束时间和起始时间一样应该是因为中间运行的过程太少了。

  • 关闭之后再运行,4个进程的输出的rank先后顺序可能会有些不同。

MPI初探:

把同一个过程分给多个线程独立执行,相互之间通过一些特殊的调用来进行通信,进程与进程之间是独立的。

并行编程与普通的编程之间看来应该是会有挺大的差别的,这一点上来看,编程的思路也需要发生挺多变化。

多机并行

MPICH除了可以在本地进行多线程并行的运算,还可以通过网络来进行远程多机多线程并行运算。

这个地方也挺坑的…

要求多台计算机上register的用户名和密码必须一致,要不然连不上。

我的两台win10都用的是微软账户,为了测试这个多机并行改了好久的本地账户。

再然后是要求如果是直接执行的本机的文件的话,要求对方的同样目录下必须有同样的文件,就是说文件也必须是同步的

完成上面所有的设置之后,然后用wmpiexec调用:

或者用命令行调用:

1
mpiexec -hosts 2 j-cf-yoga 2 j-cf-pc 2 "D:\mpitest.exe"

如果网络和设置都是正常的,应该能得到这样的结果:

多机并行完成。

并行的效率问题

这里测试了一下多个线程之间的效率比较情况:

可以比较明显地看到同样对于9999999999精度的求圆周率计算:

单线程花了10.7s,2线程5.41秒,4线程2.78秒,8线程以上效率就不会再怎么增加了

不知道是不是我的CPU只有4核的原因,还是与这个算圆周率的程序本身有关…进行到这里,已经非常能够体会到并行编程与普通程序之间的差别了:

并行程序必须要能够考虑到任务的划分、多线程之间的通讯等等,只是把原本的单线程程序拿来扔给多线程执行的话,效率根本不能增加,只是把相同的代码重复执行好多遍而已。

要想做到多线程,需要学的东西还很多。


然后是双机并行进行圆周率计算,两台电脑(Y480 i5-3210 以及 Yoga3_11 5Y10),双机8线程的时候能在1.7s左右完成上面的那个运算。

这里要再记一下的是,win10上面的防火墙太坑了…明明设了例外,还是会把通信线程给挡掉,发出指令的那一台机子需要关闭所有防火墙。回头再好好研究下。