- 浏览: 579092 次
文章分类
最新评论
MPI Debug Tips
转自:http://galoisplusplus.coding.me/blog/2013/06/08/mpi-debug-tips/
debug一个并行程序(parallel program)向来是件很麻烦的事情(Erlang
等functional
programming language另当别论), 对于像MPI这种非shared memory的inter-process model来说尤其如此。
与调试并行程序相关的工具
非开源工具
目前我所了解的商业调试器(debugger)有:
据说parallel debug的能力很屌, 本人没用过表示不知,说不定只是界面做得好看而已。 不过我想大部分人应该跟本屌一样是用不起这些商业产品的,高富帅们请无视。
以下我介绍下一些有用的open source工具:
开源工具
-Valgrind Memcheck
首先推荐valgrind
的memcheck
。
大部分MPI标准的实现(implementation)(如openmpi、mpich)支持的是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,那么执行以下命令
1
|
|
会得到类似
1
|
|
的结果。 否则请参照Valgrind User Manual 4.9. Debugging MPI Parallel Programs with Valgrind进行配置。
使用memcheck需要在compile时下-g
参数。
运行memcheck用下面的命令:
1
|
|
-Parallel Application Debugger
padb其实是个job monitor,它可以显示MPI message queue的状况。 推荐padb的一大理由是它可以检查deadlock。
使用gdb
假设你没有parallel debugger,不用担心,我们还有gdb这种serial debugger大杀器。
首先说说mpirun/mpiexec/orterun所支持的打开gdb的方式。
openmpi支持:
1
|
|
执行这个命令会打开跟所指定的进程数目一样多的终端——一下子蹦出这么多终端,神烦~——每个终端都跑有gdb。 我试过这个方式,它不支持application带有参数的[app-args]情况, 而且进程跑在不同机器上也无法正常跑起来——这一点openmpi的FAQ已经有比较复杂的解决方案。
mpich2支持:
1
|
|
但在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所讲的稍有不同:
1 2 3 4 |
|
Prof. Matloff的方法没有一个类似MPI_DEBUG
的macro。
我加这个macro只是耍下小聪明,让程序可以通过不同的编译方式生成debug模式和正常模式的可执行文件。 如果要生成debug模式的可执行文件,只需在编译时加入以下参数:
1
|
|
或
1
|
|
如果不加以上参数就是生成正常模式的可执行文件了,不会再有debug模式的副作用(例如在这里是陷入无限循环)。 不用这个macro的话,要生成正常模式的可执行文件还得回头改源代码, 这样一者可能代码很长,导致很难找到这个hook的位置; 二者如果你在「测试-发布-测试-…」的开发周期里,debug模式所加的代码经常要「加入-删掉-加入-…」很是蛋疼。
( 什么?你犯二了,在源代码中加了一句
1
|
|
好吧,你也可以不改动这一句,只需在编译时加入
1
|
|
就可以生成正常模式的可执行文件。 )
这样只需照常运行,MPI程序就会在while循环的地方卡住。 这时候打开gdb,执行
1
|
|
找到所有对应进程的pid,再用
1
|
|
attach到其中某一个进程。
Prof. Matloff用的是
1
|
|
这也是可以的。 但我习惯的是开一个gdb,要跳转到别的进程就用detach
再attach
。
让MPI程序跳出while循环:
1
|
|
现在就可以随行所欲的执行设breakpoint啊、查看register啊、print变量啊等操作了。
我猜你会这么吐嘈这种方法:每个process都要set一遍来跳出无限循环,神烦啊有木有! 是的,你没有必要每个process都加,可以只针对有代表性的process加上(例如你用到master-slave的架构那么就挑个master跟slave呗~)。
神马?「代表」很难选?! 我们可以把while循环改成:
1 2 3 4 5 |
|
这样在时间内打开gdb设好breakpoint即可,过了这段时间process就不会卡在while循环的地方。
神马?这个时间很难取?取短了来不及,取长了又猴急? 好吧你赢了……
类似的做法也被PKU的Jinlong Wu (King)博士写的调试并行程序提及到了。 他用的是:
1 2 |
|
本人没有试过,不过看起来比改源代码的方法要优秀些XD。
其他
假设你在打开gdb后会发现no
debugging symbols found
, 这是因为你的MPI可执行程序没有用于debug的symbol。 正常情况下,你在compile时下-g
参数,
生成的可执行程序(例如在linux下是ELF格式,ELF可不是「精灵」,而是Executable and Linkable Format)中会加入DWARF(DWARF是对应于「精灵」的「矮人」Debugging
With Attributed Record Format)信息。 如果你编译时加了-g
参数后仍然有同样的问题,我想那应该是你运行MPI的环境有些库没装上的缘故。
在这样的环境下,如果你不幸踩到了segmentation fault的雷区,想要debug, 可是上面的招数失效了,坑爹啊…… 好在天无绝人之路,只要有程序运行的错误信息(有core dump更好), 依靠一些汇编(assmebly)语言的常识还是可以帮助你debug的。
这里就简单以我碰到的一个悲剧为例吧, BTW为了找到bug,我在编译时没有加优化参数。 以下是运行时吐出的一堆错误信息(555好长好长的):
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 55 56 57 58 59 |
|
注意到这一行:
1 2 |
|
通过(这跟在gdb中用disas指令是一样的)
1
|
|
找到MPI_Gatherv的入口:
1
|
|
找到(MPI_Gatherv+0x116)的位置(地址52906):
1 2 3 |
|
地址为52931的<PMPI_Gatherv+0x141>之后的code主要是return,%eax应该是判断是否要return的counter。 现在寄存器%eax就成了最大的嫌疑,有理由相信猜某个对该寄存器的不正确操作导致了segmentation
fault。好吧,其实debug很多时候还得靠猜, 记得有这么个段子: 「师爷,写代码最重要的是什么?」 「淡定。」 「师爷,调试程序最重要的是什么?」
「运气。」
接下来找到了%eax被赋值的地方:
1
|
|
这里需要了解函数参数传递(function parameter passing)的调用约定(calling convention)机制:
-
对x64来说:int和pointer类型的参数依次放在
rdi
、rsi
、rdx
、rcx
、r8
、r9
寄存器中,float参数放在xmm
开头的寄存器中。 -
对x86(32bit)来说:参数放在堆栈(stack)中。 此外GNU C支持:
1
|
|
其中是一个0到3的整数,表示指定个参数通过寄存器传递,由于寄存器传参要比堆栈传参快,因而这也被称为#fastcall#。 如果指定
1
|
|
则开头的三个参数会被依次放在eax
、edx
和ecx
中。
(关于__attribute__
的详细介绍请猛戳GCC的官方文档)。
-
如果是C++的member function,别忘了隐含的第一个参数其实是object的
this
指针(pointer)。
回到我们的例子, %r8正对应MPI_Gatherv的第五個参数。 现在终于可以从底层的汇编语言解脱出来了,让我们一睹MPI_Gatherv原型的尊容:
1 2 3 |
|
第五个参数是recvcnts
,于是就可以针对这个「罪魁祸首」去看源程序到底出了什么问题了。
这里我就不贴出代码了, bug的来源就是我当时犯二了,以为这个recvcnts
是byte
number,而实际上官方文档写得明白(这里的recvcounts
就是recvcnts
):
1 2 |
|
其实是the
number of elements
啊有木有!不仔细看文档的真心伤不起! 也因为这个错误,使我的recvcnts
比recvbuf
的size要大,因而发生了access在recvbuf
范围以外的内存的情况(也就是我们从错误信息所看到的Address
not mapped
)。
最后再提一点,我源代码中的recvbuf
其实是malloc出来的内存,也就是在heap中,这种情况其实用valgrind
应该就可以检测出来(如果recvbuf
在stack中我可不能保证这一点)。所以,骚念们,编译完MPI程式先跑跑valgrind
看能不能通关吧,更重要的是,写代码要仔细看API文档减少bug。
参考资料
[1]Open MPI FAQ: Debugging applications in parallel
[3]Valgrind User Manual 4. Memcheck: a memory error detector
[4]stackoverflow: How do I debug an MPI program?
[5]Hints for Debugging Parallel Programs
[6]Compiling and Running with MPICH2 and the gdb Debugger
[7]调试并行程序
相关推荐
MPI(Multi-Programmed Input/Output)是一种通信协议,主要用于西门子的S7系列PLC(可编程逻辑控制器)之间以及PLC与上位机的通信。在工业自动化领域,MPI接口允许设备进行数据交换,实现网络化控制。本文将详细...
这个程序中,`MPI_Init`启动MPI环境,`MPI_Comm_rank`获取当前进程的ID(rank),`MPI_Comm_size`得到总进程数,最后`MPI_Finalize`结束MPI环境。 **五、并行算法** MPI可用于实现各种并行算法,如矩阵乘法、求解...
MPI,全称Message Passing Interface,是并行计算领域的一个重要标准,用于分布式内存系统中的进程间通信。MPI规范定义了一套函数接口,使得程序员可以编写跨平台、高性能的并行程序。这份“mpi官方文档”提供了MPI...
从给定的文件信息来看,讨论的主题是关于在并行计算环境下使用MPI(Message Passing Interface)实现快速傅里叶变换(FFT)。以下是对这一主题的深入解析与扩展: ### MPI与FFT结合的意义 FFT是一种高效的算法,...
标题"Microsoft的MPI和MPI_SDK"提到了两个关键概念:Microsoft MPI(微软消息传递接口)和MPI SDK(软件开发工具包)。MPI(Message Passing Interface)是一种标准化的并行计算编程接口,它允许程序员在分布式内存...
标题"MPI.zip_linux mpi"暗示了这是一个关于MPI(Message Passing Interface)的教程,专门针对Linux操作系统。MPI是分布式计算环境中的一种通信库,用于在多处理器系统或计算机集群上进行并行计算。"Linux"指的是这...
**MPI(Message Passing Interface)**是一种标准化的并行计算接口,用于编写可以在多处理器系统或分布式内存集群上运行的并行程序。MPI-Demo是一个基于MPI的演示项目,旨在帮助用户理解和学习MPI的基本用法。 MPI...
MPI课件(MPI并行程序设计)MPI课件(MPI并行程序设计)MPI课件(MPI并行程序设计)MPI课件(MPI并行程序设计)MPI课件(MPI并行程序设计)MPI课件(MPI并行程序设计)MPI课件(MPI并行程序设计)
西门子MPI(Multi-Point Interface)是一种通信协议,它被广泛应用于西门子的SIMATIC S7-300和S7-400系列PLC(可编程逻辑控制器)中,允许PLC与其他设备如编程设备、人机界面(HMI)、其他PLC或者上位机进行数据交换...
这个代码用于测试mpi全局通信,针对不同的情况,分别使用了MPI_Alltoall和MPI_Alltoallv
本文通过介绍 MPI 时间函数的测试,展示了 MPI 中一些基本函数的使用,包括 MPI_Wtime、MPI_Wtick、MPI_Init、MPI_Finalize、MPI_Comm_rank、MPI_Comm_size、MPI_Recv 和 MPI_Send 函数。这些函数是 MPI 中的基本...
在Linux环境下,使用配置号的mpi库,实现多进程并发执行。是一个mpi初步小练习。适合初学mpi人练习使用 ****************************************************** ****这里请注意,是在Linux下编译和运行的。编译...
**MPI(Message Passing Interface)**是一种标准化的并行计算接口,用于编写可以在分布式内存系统上运行的高性能并行程序。MPI提供了一套丰富的通信原语,使得程序员可以控制进程间的通信,实现数据交换和同步。在...
MPI(Message Passing Interface)是一种标准,用于在分布式内存系统中进行进程间通信,如并行计算机和集群。这个“MPI函数的参考手册”是程序员在使用MPI编程时的重要参考资料,它详细列出了MPI库中主要函数的功能...
4. **MPI通信原语**:MATLABMPI暴露了MPI通信原语,如`mpi.send`和`mpi.recv`,使得可以直接使用低级别通信操作。这些函数可以实现进程间的数据传输,比如点对点的消息传递、广播和集合通信。 5. **并行数组和...
Fortran MPI程序设计是针对高性能计算领域的一种编程技术,它结合了Fortran语言的强大科学计算能力与MPI(Message Passing Interface)的并行处理框架。Fortran作为一门历史悠久且专为科学计算优化的语言,广泛应用...
### MPI函数帮助文档知识点概述 #### 一、MPI简介与基本概念 MPI(Message Passing Interface,消息传递接口)是一种广泛使用的并行编程模型标准,它为高性能计算领域提供了跨多种平台的消息传递通信功能。MPI支持...
### MPI for Python Manual #### 一、引言与概述 **MPI for Python** 是一个用于 Python 编程语言的消息传递接口 (MPI) 的绑定库,它由 Lisandro Dalcín 开发并发布于 2009 年 12 月 29 日。该文档介绍了 MPI for...
在C语言中,MPI提供了一系列的函数调用,如`MPI_Init()`用于初始化MPI环境,`MPI_Comm_rank()`获取当前进程的ID,`MPI_Send()`和`MPI_Recv()`实现数据发送和接收。理解这些基本操作是掌握MPI编程的基础。 OpenMP,...
在分布式计算领域,MPI(Message Passing Interface)是一种广泛使用的编程接口,用于构建并行应用程序。本文将深入探讨如何使用MPI来计算圆周率π的值,这是一个经典的并行计算问题,有助于初学者理解MPI的基本原理...