mpi集群配置mpirun并行,把同样的任务重复跑了几十遍

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

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

说不定只是界面做得好看而已
不过我想大部分人应该跟本屌一样是用不起这些商业产品的,
以下我介绍下一些有用的open source工具:

有些时候所谓的MPI程序的bug,不过是一般sequential程序常见的内存错误罢了。
这个时候用memcheck检查就可以很容易找到bug的藏身之处。
不会有那些naive的问题,
但我还是建议你使用memcheck检查你程序的可执行文件,
因为memcheck除了检查内存错误,
用来发送消息的buffer大小小于MPI_Send所指定的大小、
用来接受消息的buffer大小小于MPI_Recv所指定的大小等等,
我想你的那些方法应该对这些不管用吧?

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

执行这个命令会打开跟所指定的进程数目一样多的终端——一下子蹦出这么多终端,神烦~——每个终端都跑有gdb。
而且进程跑在不同机器上也无法正常跑起来——这一点已经有比较复杂的解决方案。

详情请猛戳这个链接(。
像我机器上的mpich版本是3.0.3,所以这个选项也就不能用了。
如果你想试试可以用包含MPD的旧版mpich。

好,以下假设我们不用上述方式,只是像debug一般的程序一样,打开gdb,attach到相应进程,完事,detach,退出。
现在我们要面对的一大问题其实是怎么让MPI程序暂停下来。
因为绝大多数MPI程序其实执行得非常快——写并行程序的一大目的不就是加速么——很多时候来不及打开gdb,MPI程序就已经执行完了。
所以我们需要让它先缓下来等待我们打开gdb执行操作。

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

我加这个macro只是耍下小聪明,让程序可以通过不同的编译方式生成debug模式和正常模式的可执行文件。
如果要生成debug模式的可执行文件,只需在编译时加入以下参数:

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

什么?你犯二了,在源代码中加了一句

好吧,你也可以不改动这一句,只需在编译时加入

就可以生成正常模式的可执行文件。

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

找到所有对应进程的pid,再用

attach到其中某一个进程。

但我习惯的是开一个gdb,要跳转到别的进程就用detachattach

现在就可以随行所欲的执行设breakpoint啊、查看register啊、print变量啊等操作了。

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

神马?「代表」很难选?!
我们可以把while循环改成:

神马?这个时间很难取?取短了来不及,取长了又猴急?

本人没有试过,不过看起来比改源代码的方法要优秀些XD。

这是因为你的MPI可执行程序没有用于debug的symbol。
正常情况下,你在compile时下-g参数,
如果你编译时加了-g参数后仍然有同样的问题,我想那应该是你运行MPI的环境有些库没装上的缘故。
可是上面的招数失效了,坑爹啊......
好在天无绝人之路,只要有程序运行的错误信息(有core dump更好),
依靠一些汇编(assmebly)语言的常识还是可以帮助你debug的。

这里就简单以我碰到的一个悲剧为例吧,
BTW为了找到bug,我在编译时没有加优化参数。
以下是运行时吐出的一堆错误信息(555好长好长的):

通过(这跟在gdb中用disas指令是一样的)

现在寄存器%eax就成了最大的嫌疑,有理由 相信 猜某个对该寄存器的不正确操作导致了segmentation fault。
好吧,其实debug很多时候还得靠猜,
「师爷,写代码最重要的是什么?」
「师爷,调试程序最重要的是什么?」

接下来找到了%eax被赋值的地方:

  • 对x86(32bit)来说:参数放在堆栈(stack)中。

其中<number>是一个0到3的整数,表示指定<number>个参数通过寄存器传递,由于寄存器传参要比堆栈传参快,因而这也被称为#fastcall#。

则开头的三个参数会被依次放在eaxedxecx中。

现在终于可以从底层的汇编语言解脱出来了,让我们一睹MPI_Gatherv原型的尊容:

第五个参数是recvcnts,于是就可以针对这个「罪魁祸首」去看源程序到底出了什么问题了。
这里我就不贴出代码了,

最后再提一点,我源代码中的recvbuf其实是malloc出来的内存,也就是在heap中,这种情况其实用valgrind应该就可以检测出来(如果recvbuf在stack中我可不能保证这一点)。所以,骚念们,编译完MPI程式先跑跑valgrind看能不能通关吧,更重要的是,写代码要仔细看API文档减少bug。

从1986年到2002年,微处理器的性能以平均50%的速度不断提升。但是,从2002年开始,单处理器的性能提升速度降到每年大约20%,这个差距是巨大的:如果以每年50%的速度提升,在10年里,微处理器的性能能提升60倍,而如果以20%的速度提升的话,10时间里,只能提升6倍。所以,从2005年起,大部分主流的CPU制造商决定通过并行处理来快速提升微处理器的性能。他们不再继续开发速度更快的单处理器芯片,而是开始将多个完整的单处理器放到一个集成电路芯片上。这一变化对软件人员带来了重大的影响,也由此引出了一系列的问题:

  • 为什么需要不断提升的性能?

过去的几十年,借助不断提升的计算能力,人类在许多领域(如:人类基因解码、医疗成像、人工智能、虚拟现实等)发展的非常迅速。但随着人类在科学步伐的前进,这里领域的许多应用在计算能力上的要求也越来越高,解决问题的规模也在不断的增加。

  • 为什么需要构建并行系统?

单处理器性能大幅度提升的主要原因之一是日益增加的集成电路晶体管密度。随着晶体管尺寸减小,传递速度增快,集成电路整体的速度也加快,它们的能耗也相应增加。大多数能量是以热能的形式消耗,而在21世纪的前10年中,用空气冷却的集成电路的散热能力已经达到极限了,所以,集成电脑制造商的决策是:与其构建更快、更复杂的单处理器,不如在芯片上放置多个相对简单的处理器,这样的有多个处理器的集成电脑称之为多核处理器。

  • 为什么需要编写并行程序?

    通常我们传统单核处理器上编写的程序无法利用多核处理器,我们需要使得程序充分利用处理器更快的运行程序,更加及时与逼真的模拟现实世界。为了达到这一目的,就需要软件开发工程师将串行程序改写为并行程序。

广泛采用的两种方式:任务并行和数据并行。

任务并行:是指将有待解决的问题需要执行的任务分配到各个核上完成。

数据并行:是指将有待解决的问题所需要处理的数据分配到各个核上完成,每个核在所分配的大致相当的数据集上执行相同操作。

这里我们举个例子来说明什么是任务并行,什么是数据并行?假如一个学校的高三总共有200个学生,有5个数学老师,分别是A、B、C、D、E老师,到期末考试的时候,数学试卷出了5道题目进行考试,在阅卷的时候,有两种方案:每个人负责一道题目进行打分;或者将学生分成5组,每人负责一组,即20个学生。在这两种方案中,数学老师充当计算机核的角色。

在第一种方案中,可以认为是任务并行的例子。有五个任务(题目)需要执行,即给批阅第一道题、批阅第二道题、……批阅第五道题。每个人执行的指令是不相同的。应该每一道题的内容不同。

而第二种方案中,可以认为数据并行的例子,“数据”就是学生的试卷,在不同的核(打分人)之间平分,每个核执行大致相同的指令。

我们目前的计算机都是基于经典的冯偌伊曼结构的:主存+CPU+总线(互连设备)。根据Flynn(费林)分类法,即基于指令和数据流方式,可以将并行计算机划分为:SISD、MISD、SIMD和MIMD四种。具体如下图:

通俗的讲,SISD系统即是我们典型的冯诺依曼系统,它是一次执行一条指令,一次存取一个数据项。而其它三个才是真正的并行计算系统。SIMD系统通过对多个数据执行相同的指令,从而实现在多个数据流上的操作,因此,该系统非常适合处理大型数组的简单循环,从此衍生出向量处理器,重点对数组或数据向量进行操作。这里重点说明一下,我们常说的GPU(graphics processing unit)图形处理器计算机就是讲物体表面的内部表示为使用点、线、面的像素数组来表示的,也是一个概念上的SIMD系统,不过随着技术的发展,现代的GPU也可以有几十个核,每个核也能独立的执行指令流。

在并行计算中,一般将MIMD作为主要研究对象。其中MIMD系统主要有两种类型:共享内存系统(图5)和分布式内存系统(图6)。

在共享内存系统中,自治的CPU通过互连网络与内存系统相连,每个CPU都能访问每个内存系统中的内存。分布式内存系统中,每个CPU都有自己的独立内存,每个单元之间的内存访问通过消息通讯或者特殊的函数来进行。

SPMD(单程序多数据流):指的不是在每个核上运行不同的程序,而是一个程序仅包含一份代码,通过一定的条件分支,使得这一份代码再执行的时候表现的像是在不同的处理器上执行不同的代码。

在我们主要讨论的MIMD分布式内存系统中,各个核能够直接访问自己的内存,而运行在不同核之间的进程需要交换内存数据的时候,只能通过消息传递API来实现。消息传递的API至少要提供一个发送函数和接收函数。进程之间通过它们的序号(rank)进行识别。

我们编写并行计算程序的主要目的当然是为了提供它们的性能,那么我们应该如何去评价一个并行计算程序的性能呢?***是:加速比效率

理论上而言,如果使用p个进程或者线程来运行一个并行程序,并且每个核中之多只有一个进程/线程,那么,S=p,E=1。但是在实际情况下,S<p,E<1。既然已经知道如何评价一个将串行程序改为并行程序的性能,那么现在假设我们已经有了一个串行程序,为了提高性能,我们如何将串行程序改为高性能的并行计算程序呢?需要哪些步骤?

一般情况下,需要将工作进行拆分,使得分布在每个进程中的工作量大致相仿,并行让它们之间的通信量最少。于是串行程序并行化设计步骤一般按以下几步进行:

第一步:划分。将串行程序中需要要执行的指令和数据按照计算部分拆分成多个小任务。关键:识别出可以进行并行执行的任务。

第二步:通讯。确定第一步识别出来的任务之间需要执行那些通信。

第三步:凝聚/聚合。将第一步确定的任务与通信结合成更大的任务。

第四步:分配。将上一步聚合好的任务分配到进程/线程中。这一步还主要注意的是,要使得通信量最小化,让各个进程/线程所得到的工作量大致均衡。

MPI实现并行是进程级;采用的是分布式内存系统,显式(数据分配方式)实现并行执行,通过通信在进程之间进行消息传递,可扩展性好。MPI虽适合于各种机器,但它的编程模型复杂:

  • 需要分析及划分应用程序问题,并将问题映射到分布式进程集合;
  • 需要解决通信延迟大和负载不平衡两个主要问题;
  • MPI程序可靠性差,一个进程出问题,整个程序将错误;

Pthreads实现并行是线程级;采用的是共享内存系统,只有在POSIX的系统(linux、mac OS X、Solaris、HPUX等)上才有效。它是一个可以连接到C程序中的库,目前标准的C++共享内存线程库还在开发中,也许在将来在C++程序中,使用这个库更加方便。

OpenMP是线程级(并行粒度);采用的是共享内存系统,隐式(数据分配方式)实现并行执行;可扩展性差;正因为采用共享内存分布系统,意味着它只适应于SMP(Symmetric Multi-Processing 对称多处理结构),DSM(Distributed Shared Memory 共享内存分布系统)机器,不适合于集群。

  全称Open Computing Language,开放运算语言。是一个为异构平台编写程序的框架,此异构平台可由CPU,GPU或其他类型的处理器组成。OpenCL由一门用于编写kernels (在OpenCL设备上运行的函数)的语言(基于C99)和一组用于定义并控制平台的API组成。OpenCL提供了基于任务分割和数据分割的并行计算机制。OpenCL类似于另外两个开放的工业标准OpenGL和OpenAL,这两个标准分别用于三维图形和计算机音频方面。

GPU是专门为执行复杂的数学和集合计算而设计的,一个GPU内有许多流处理簇(stream Multiprocessor)SM,他们就类似于CPU的核。这些SM与共享(一级缓存)连接在一起,然后又与相当于SM间互联开关的二级缓存相连。类似于这种设计,都是为计算图形渲染所必须的。

是谷歌公司MapReduce框架的一个开源版本。它针对的是linux平台。其概念是你取来一个大数据集,然后将其切割或映射(map)成很多小的数据块。然而,并不是将数据发送到各个节点,取而代之的是数据集通过并行文件系统已经被划分给上百或者上千个节点。因此,归约(Reduce)步骤就是把程序发送到已经包含数据的节点上,然后输出结果写入本地节点并保存在那里。

扫扫二维码,随身浏览文档

手机或平板扫扫即可继续访问

并行环境基于IB和以太网的***步骤 - 中国科学院紫金山天文台

参考资料

 

随机推荐