MPI并行程序的调试技巧

原文地址:http://galoisplusplus.gitcafe.com/blog/2013/06/08/mpi-debug-tips/


debug一个并行程序(parallel program)向来是件很麻烦的事情(```Erlang```等functional programming language另当别论),
对于像MPI这种非shared memory的inter-process model来说尤其如此。


## 与调试并行程序相关的工具 ##


### 非开源工具 ###


目前我所了解的商业调试器(debugger)有:


- [TotalView](http://www.roguewave.com/products/totalview.aspx)
- [Allinea DDT](http://www.allinea.com/products/ddt/)


据说parallel debug的能力很屌,
本人没用过表示不知,
说不定只是界面做得好看而已
不过我想大部分人应该跟本屌一样是用不起这些商业产品的,
高富帅们请无视
以下我介绍下一些有用的open source工具:


### 开源工具 ###


#### - [Valgrind Memcheck](http://valgrind.org) #####


首先推荐```valgrind```的```memcheck```。
大部分MPI标准的实现(implementation)(如[openmpi](http://www.open-mpi.org/)、[mpich](http://www.mpich.org/))支持的是C、C++和Fortran语言。
Fortran语言我不了解,但C和C++以复杂的内存管理(memory management)见长可是出了名的XD。
有些时候所谓的MPI程序的bug,不过是一般sequential程序常见的内存错误罢了。
这个时候用memcheck检查就可以很容易找到bug的藏身之处。
你可能会争论说你用了RAII(Resource Allocation Is Initialization)等方式来管理内存,
不会有那些naive的问题,
但我还是建议你使用memcheck检查你程序的可执行文件,
因为memcheck除了检查内存错误,
还可以检查message passing相关的错误,
例如:MPI\_Send一块没有完全初始化的buffer、
用来发送消息的buffer大小小于MPI\_Send所指定的大小、
用来接受消息的buffer大小小于MPI\_Recv所指定的大小等等,
我想你的那些方法应该对这些不管用吧?


这里假设你已经安装并配置好了memcheck,例如如果你用的是openmpi,那么执行以下命令


```bash
ompi_info | grep memchecker
```
会得到类似


```bash
MCA memchecker: valgrind (MCA v2.0, API v2.0, Component v1.6.4)
```
的结果。
否则请参照[Valgrind User Manual 4.9. Debugging MPI Parallel Programs with Valgrind](http://valgrind.org/docs/manual/mc-manual.html#mc-manual.mpiwrap)进行配置。


使用memcheck需要在compile时下```-g```参数。
运行memcheck用下面的命令:
```
mpirun [mpirun-args] valgrind [valgrind-args] [app-args]
```





#### - [Parallel Application Debugger](http://padb.pittman.org.uk/) ####


padb其实是个job monitor,它可以显示MPI message queue的状况。
推荐padb的一大理由是它可以检查deadlock。


## 使用gdb ##


假设你没有parallel debugger,不用担心,我们还有gdb这种serial debugger大杀器。


首先说说mpirun/mpiexec/orterun所支持的打开gdb的方式。


openmpi支持:
```
mpirun [mpirun-args] xterm -e gdb
```
执行这个命令会打开跟所指定的进程数目一样多的终端——一下子蹦出这么多终端,神烦~——每个终端都跑有gdb。
我试过这个方式,它不支持application带有参数的[app-args]情况,
而且进程跑在不同机器上也无法正常跑起来——这一点[openmpi的FAQ](http://www.open-mpi.org/faq/?category=debugging)已经有比较复杂的解决方案。


mpich2支持:
```
mpirun -gdb
```
但在mpich较新的版本中,该package的进程管理器(process manager)已经从MPD换为Hydra,这个```-gdb```的选项随之消失。
详情请猛戳这个链接(http://trac.mpich.org/projects/mpich/ticket/1150)。
像我机器上的mpich版本是3.0.3,所以这个选项也就不能用了。
如果你想试试可以用包含MPD的旧版mpich。


好,以下假设我们不用上述方式,只是像debug一般的程序一样,打开gdb,attach到相应进程,完事,detach,退出。

现在我们要面对的一大问题其实是怎么让MPI程序暂停下来。
因为绝大多数MPI程序其实执行得非常快——写并行程序的一大目的不就是加速么——很多时候来不及打开gdb,MPI程序就已经执行完了。
所以我们需要让它先缓下来等待我们打开gdb执行操作。


目前比较靠谱的方法是在MPI程序里加hook,这个方法我是在UCDavis的Professor Matloff的主页上看到的(猛戳这里:http://heather.cs.ucdavis.edu/~matloff/pardebug.html)。
不过我喜欢的方式跟Prof.Matloff所讲的稍有不同:


```c
#ifdef MPI_DEBUG
int gdb_break = 1;
while(gdb_break) {};
#endif
```


Prof. Matloff的方法没有一个类似```MPI_DEBUG```的macro。
我加这个macro只是耍下小聪明,让程序可以通过不同的编译方式生成debug模式和正常模式的可执行文件。
如果要生成debug模式的可执行文件,只需在编译时加入以下参数:
```
-DMPI_DEBUG
```

```
-DMPI_DEBUG=define
```
如果不加以上参数就是生成正常模式的可执行文件了,不会再有debug模式的副作用(例如在这里是陷入无限循环)。
不用这个macro的话,要生成正常模式的可执行文件还得回头改源代码,
这样一者可能代码很长,导致很难找到这个hook的位置;
二者如果你在「测试-发布-测试-...」的开发周期里,debug模式所加的代码经常要「加入-删掉-加入-...」很是蛋疼。



什么?你犯二了,在源代码中加了一句
```c
#define MPI_DEBUG
```
好吧,你也可以不改动这一句,只需在编译时加入
```
-UMPI_DEBUG
```
就可以生成正常模式的可执行文件。





这样只需照常运行,MPI程序就会在while循环的地方卡住。
这时候打开gdb,执行


```
(gdb) shell ps aux | grep
```
找到所有对应进程的pid,再用
```
(gdb) attach
```
attach到其中某一个进程。


Prof. Matloff用的是
```
gdb
```
这也是可以的。
但我习惯的是开一个gdb,要跳转到别的进程就用```detach```再```attach```。


让MPI程序跳出while循环:
```
(gdb) set gdb_break = 0
```
现在就可以随行所欲的执行设breakpoint啊、查看register啊、print变量啊等操作了。


我猜你会这么吐嘈这种方法:每个process都要set一遍来跳出无限循环,神烦啊有木有!
是的,你没有必要每个process都加,可以只针对有代表性的process加上(例如你用到master-slave的架构那么就挑个master跟slave呗~)。


神马?「代表」很难选?!
我们可以把while循环改成:
```c
while(gdb_break)
{
// set the sleep time to pause the processes
sleep(

你可能感兴趣的:(MPI)