我有一个编译和运行的MPI程序,但我想逐步完成它以确保没有任何奇怪的事情发生。理想情况下,我想要一种简单的方法将GDB附加到任何特定的进程,但我不确定这是否可行或如何执行。另一种方法是让每个进程将调试输出写入单独的日志文件,但这并不能给调试器提供相同的自由。
有更好的方法吗?你如何调试MPI程序?
答案 0 :(得分:73)
我发现gdb非常有用。我用它作为
mpirun -np <NP> xterm -e gdb ./program
这将启动我可以做的xterm窗口
run <arg1> <arg2> ... <argN>
通常可以正常使用
您还可以使用以下方法将这些命令打包在一起:
mpirun -n <NP> xterm -hold -e gdb -ex run --args ./program [arg1] [arg2] [...]
答案 1 :(得分:57)
正如其他人所说,TotalView是此标准。但它会让你失去一条腿和一条腿。
OpenMPI网站有一个很棒的FAQ on MPI debugging。 FAQ中的第6项描述了如何将GDB附加到MPI进程。阅读整篇文章,有一些很棒的提示。
如果您发现有太多的流程无法跟踪,请查看Stack Trace Analysis Tool (STAT)。我们在利弗莫尔使用它来收集可能数十万个正在运行的进程的堆栈跟踪,并智能地向用户表示它们。它不是一个功能齐全的调试器(一个功能齐全的调试器永远不会扩展到208k核心),但它会告诉你哪些进程组正在做同样的事情。然后,您可以在标准调试器中逐步查看每个组的代表。
答案 2 :(得分:23)
这里的许多帖子都是关于GDB的,但是没有提到如何从启动时附加到进程。显然,您可以附加到所有流程:
mpiexec -n X gdb ./a.out
但是,由于您必须反弹以启动所有进程,因此效果极差。如果您只想调试一个(或少量)MPI进程,可以使用:
运算符在命令行上将其添加为单独的可执行文件:
mpiexec -n 1 gdb ./a.out : -n X-1 ./a.out
现在只有一个进程会获得GDB。
答案 3 :(得分:17)
正如其他人所提到的,如果你只是使用少数的MPI流程,你可以尝试使用multiple gdb sessions,更可靠的valgrind或滚动你自己的printf / logging solution。
如果您使用的是更多进程,那么您真的需要一个合适的调试器。 OpenMPI FAQ建议使用Allinea DDT和TotalView。
我在Allinea DDT工作。它是一个功能齐全的图形源代码调试器,所以是的,你可以:
......等等。如果你已经使用过Eclipse或Visual Studio,那么你就会在家里。
我们添加了一些有趣的功能,专门用于调试并行代码(无论是MPI,多线程还是CUDA):
自动比较所有进程的标量变量:
您还可以在进程和时间内跟踪和过滤变量和表达式的值:
它在top500 HPC网站中广泛使用,例如ORNL,NCSA,LLNL,Jülich等。人
界面非常活泼;作为橡树岭捷豹集群验收测试的一部分,我们定时步进和合并220,000个过程的堆栈和变量,为0.1秒。
@tgamblin提到了与STAT集成的优秀Allinea DDT,以及其他几个流行的开源项目。
答案 4 :(得分:8)
答案 5 :(得分:5)
http://github.com/jimktrains/pgdb/tree/master是我写的一个实用程序。有一些文档,请随时向我提问。
你基本上调用一个包装GDB的perl程序,并将它的IO汇集到一个中央服务器上。这允许GDB在每个主机上运行,并且您可以在终端的每个主机上访问它。
答案 6 :(得分:5)
将screen
与gdb
一起使用来调试MPI应用程序可以很好地工作,尤其是在xterm
不可用或您正在处理多个处理器的情况下。随之而来的堆栈溢出搜索有许多陷阱,所以我将完整地重现我的解决方案。
首先,在MPI_Init之后添加代码以打印出PID并暂停程序以等待您附加。标准解决方案似乎是一个无限循环;我最终确定了raise(SIGSTOP);
,这需要额外调用continue
才能在gdb内进行转义。
}
int i, id, nid;
MPI_Comm_rank(MPI_COMM_WORLD,&id);
MPI_Comm_size(MPI_COMM_WORLD,&nid);
for (i=0; i<nid; i++) {
MPI_Barrier(MPI_COMM_WORLD);
if (i==id) {
fprintf(stderr,"PID %d rank %d\n",getpid(),id);
}
MPI_Barrier(MPI_COMM_WORLD);
}
raise(SIGSTOP);
}
编译后,在后台运行可执行文件,然后捕获stderr。然后,您可以{st {1} stderr文件中的某个关键字(此处为文字PID)以获取每个进程的PID和等级。
grep
可以使用MDRUN_EXE=../../Your/Path/To/bin/executable
MDRUN_ARG="-a arg1 -f file1 -e etc"
mpiexec -n 1 $MDRUN_EXE $MDRUN_ARG >> output 2>> error &
sleep 2
PIDFILE=pid.dat
grep PID error > $PIDFILE
PIDs=(`awk '{print $2}' $PIDFILE`)
RANKs=(`awk '{print $4}' $PIDFILE`)
将gdb会话附加到每个进程。在屏幕会话中这样做可以轻松访问任何gdb会话。 gdb $MDRUN_EXE $PID
以分离模式启动屏幕,-d -m
允许您命名屏幕以便以后轻松访问,而bash的-S "P$RANK"
选项以交互模式启动它并使gdb不立即退出。
-l
一旦gdb在屏幕中启动,您可以使用屏幕的for i in `awk 'BEGIN {for (i=0;i<'${#PIDs[@]}';i++) {print i}}'`
do
PID=${PIDs[$i]}
RANK=${RANKs[$i]}
screen -d -m -S "P$RANK" bash -l -c "gdb $MDRUN_EXE $PID"
done
命令向屏幕输入脚本(这样您就不必输入每个屏幕并输入相同的内容)。命令末尾需要换行符。这里的屏幕由-X stuff
使用之前给出的名称访问。 -S "P$i"
选项很关键,否则命令会间歇性地失败(根据您之前是否已连接到屏幕)。
-p 0
此时,您可以使用for i in `awk 'BEGIN {for (i=0;i<'${#PIDs[@]}';i++) {print i}}'`
do
screen -S "P$i" -p 0 -X stuff "set logging file debug.$i.log
"
screen -S "P$i" -p 0 -X stuff "set logging overwrite on
"
screen -S "P$i" -p 0 -X stuff "set logging on
"
screen -S "P$i" -p 0 -X stuff "source debug.init
"
done
附加到任何屏幕,并使用screen -rS "P$i"
分离。可以将命令发送到所有gdb会话,类似于上一段代码。
答案 7 :(得分:4)
如果您是tmux
用户,使用 Benedikt Morbach 脚本会感觉很舒服:tmpi
原始来源: https://github.com/moben/scripts/blob/master/tmpi
福克:https://github.com/Azrael3000/tmpi
通过它,您可以同步多个面板(进程数)(同时在所有面板或进程上复制每个命令,因此与xterm -e
方法相比可节省大量时间)。此外,您可以在想要执行print
的过程中了解变量的值,而无需移动到另一个面板,这将在每个面板上打印每个过程的变量值。
如果您不是tmux
用户,我强烈建议您尝试并查看。
答案 8 :(得分:3)
调试MPI程序的“标准”方法是使用支持该执行模型的调试器。
在UNIX上,据说TotalView对MPI有很好的支持。
答案 9 :(得分:2)
我使用这个小的homebrewn方法将调试器附加到MPI进程 - 在代码中的MPI_Init()之后调用以下函数DebugWait()。现在,当进程正在等待键盘输入时,您有足够的时间将调试器附加到它们并添加断点。完成后,提供单个字符输入,您就可以开始使用了。
static void DebugWait(int rank) {
char a;
if(rank == 0) {
scanf("%c", &a);
printf("%d: Starting now\n", rank);
}
MPI_Bcast(&a, 1, MPI_BYTE, 0, MPI_COMM_WORLD);
printf("%d: Starting now\n", rank);
}
当然,您只想为调试版本编译此函数。
答案 10 :(得分:2)
还有我的开源工具padb,旨在帮助进行并行编程。我将其称为“作业检查工具”,因为它不仅可以用作调试器,还可以用作例如并行顶级程序。在“完整报告”模式下运行,它将显示应用程序中每个进程的堆栈跟踪以及每个级别上每个函数的局部变量(假设您使用-g编译)。它还会显示“MPI消息队列”,即作业中每个等级的未完成发送和接收列表。
除了显示完整报告之外,还可以告诉padb放大作业中的各个信息位,有无数的选项和配置项来控制显示的信息,请参阅网页了解更多信息的信息。
答案 11 :(得分:1)
答案 12 :(得分:1)
一种调试MPI程序的简单方法。
在main()函数中添加睡眠(some_seconds)
像往常一样运行程序
$ mpirun -np <num_of_proc> <prog> <prog_args>
程序将启动并进入睡眠状态。
因此,您将有几秒钟的时间来通过ps查找您的进程,运行gdb并将其附加。
如果您使用QtCreator之类的编辑器,则可以使用
Debug->开始调试->附加到运行的应用程序
并在那里找到您的处理程序。
答案 13 :(得分:1)
答案 14 :(得分:1)
我使用日志跟踪执行一些与MPI相关的调试,但如果您使用mpich2:MPICH2 and gdb,也可以运行gdb。当您处理从调试器启动时难以处理的过程时,这种技术通常是一种很好的做法。
答案 15 :(得分:0)
另一种解决方案是在SMPI(模拟MPI)中运行代码。这是一个我参与的开源项目。每个MPI排名都将转换为同一UNIX进程的线程。然后,您可以轻松使用gdb来执行MPI排名。
SMPI为MPI应用的研究提出了其他优势:clairevoyance(您可以观察系统的每个部分),可重复性(除非您指定,否则几次运行会导致完全相同的行为),没有heisenbugs(作为模拟平台)与主持人保持不同等)
有关详细信息,请参阅this presentation或related answer。
答案 16 :(得分:0)
您可以使用免费的Visual Studio代码,并且比xterm
更容易使用。您复制“ VS代码”窗口,然后将调试器手动附加到每个进程。请参阅以下视频中的说明: