Answers:
就像别人说的,TotalView是为此的标准。但这会花费您一臂之力。
OpenMPI网站提供了有关MPI调试的常见问题解答。FAQ中的第6项描述了如何将GDB附加到MPI流程。阅读全文,这里有一些很棒的技巧。
但是,如果发现您要跟踪的进程太多,请签出堆栈跟踪分析工具(STAT)。我们在Livermore上使用它来收集可能来自成千上万个正在运行的进程的堆栈跟踪,并将其智能地呈现给用户。它不是功能齐全的调试器(功能全面的调试器永远不会扩展到208k内核),但它会告诉您哪些进程组正在执行相同的操作。然后,您可以在标准调试器中逐步浏览每个组的代表。
我发现gdb非常有用。我用它
mpirun -np <NP> xterm -e gdb ./program
这会启动xterm Windows,我可以在其中执行此操作
run <arg1> <arg2> ... <argN>
通常工作正常
您还可以使用以下命令将这些命令打包在一起:
mpirun -n <NP> xterm -hold -e gdb -ex run --args ./program [arg1] [arg2] [...]
<file>
并传递-x <file>
给gdb来执行此操作。
正如其他人提到的那样,如果您仅使用少数 MPI进程,则可以尝试使用多个gdb session,可重用的valgrind或滚动自己的printf / logging解决方案。
如果您使用的进程更多,那么您真的开始需要一个合适的调试器。该的openmpi FAQ建议都Allinea DDT和TotalView软件。
我在Allinea DDT上工作。它是功能齐全的图形化源代码调试器,因此可以,您可以:
...等等。如果您使用过Eclipse或Visual Studio,那么您就可以在家了。
我们添加了一些有趣的功能,专门用于调试并行代码(MPI,多线程或CUDA):
标量变量会在所有过程中自动进行比较:(
来源:allinea.com)
您还可以跟踪和过滤过程和时间内的变量和表达式的值:
它在前500个 HPC站点中被广泛使用,例如ORNL,NCSA,LLNL,Jülich等。等
界面非常活泼;在Oak Ridge的Jaguar集群进行验收测试的过程中,我们定时在0.1s的时间内步进和合并220,000个进程的堆栈和变量。
@tgamblin提到了出色的STAT,它与Allinea DDT集成在一起,还有其他一些流行的开源项目。
如果您是tmux
用户,那么使用Benedikt Morbach的脚本会感到非常满意:tmpi
原始资料: https://github.com/moben/scripts/blob/master/tmpi
叉:https : //github.com/Azrael3000/tmpi
有了它,您可以同步所有多个面板(进程数)(每个命令同时复制到所有面板或进程上,因此与该xterm -e
方法相比可以节省很多时间)。此外,您可以在想要执行的过程中知道变量的值print
而不必移动到另一个面板,这将在每个面板上打印每个过程的变量值。
如果您不是tmux
用户,我强烈建议您尝试一下。
http://github.com/jimktrains/pgdb/tree/master是我编写的用于执行此操作的实用程序。有一些文档,随时可以发问我。
您基本上可以调用一个包装GDB并将其IO集中到中央服务器的perl程序。这使GDB可以在每个主机上运行,并且可以在终端的每个主机上访问它。
使用screen
连同gdb
调试MPI应用程序工作得很好,尤其是如果xterm
不可用,或者你正在处理超过几个处理器。伴随着stackoverflow搜索的过程中有很多陷阱,所以我将完整地再现我的解决方案。
首先,在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。然后grep
,您可以使用stderr文件中的某个关键字(此处为文字PID)来获取每个进程的PID和等级。
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 $MDRUN_EXE $PID
。在屏幕会话中执行此操作可以轻松访问任何gdb会话。-d -m
以分离模式启动屏幕,-S "P$RANK"
允许您命名屏幕以便以后访问,并且-l
bash选项以交互模式启动屏幕,并防止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
一旦gdb在屏幕中启动,您就可以使用screen的-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"
并使用分离Ctrl+A+D
。可以将命令发送到所有gdb会话,类似于上一节的代码。
我还有一个开源工具padb,旨在帮助进行并行编程。我称它为“作业检查工具”,因为它不仅可以用作调试器,还可以用作例如并行程序之类的程序。在“完整报告”模式下运行,它将向您显示应用程序中每个进程的堆栈跟踪以及每个级别上每个函数的局部变量(假设您使用-g编译)。它还将向您显示“ MPI消息队列”,即作业中每个级别的未完成发送和接收的列表。
除了显示完整的报告外,还可以告诉padb放大作业中的各个信息,还有无数的选项和配置项来控制显示哪些信息,有关更多详细信息,请参见网页。
我使用这个小的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);
}
当然,您只想为调试版本编译此函数。
gethostname(hostname, sizeof(hostname)); printf("PID %d on host %s ready for attach\n", getpid(), hostname);
。然后,您通过键入rsh <hostname_from_print_statement>
,最后是来附加到流程gdb --pid=<PID_from_print_statement>
。
我使用日志跟踪进行了一些与MPI相关的调试,但是如果您使用的是mpich2,也可以运行gdb:MPICH2和gdb。通常,在处理从调试器启动时比较棘手的过程时,此技术通常是一种好习惯。