如何调试 MPI 程序?

我有一个编译和运行的 MPI 程序,但我想逐步通过它,以确保没有什么奇怪的事情发生。理想情况下,我希望有一种简单的方法将 GDB 附加到任何特定的进程,但我不确定这是否可行或如何做到这一点。另一种方法是让每个进程将调试输出写入一个单独的日志文件,但这并没有给予调试器同样的自由度。

有没有更好的方法? 如何调试 MPI 程序?

107291 次浏览

调试 MPI 程序的“标准”方法是使用支持该执行模型的调试器。

在 UNIX 上,据说 TotalView对 MPI 有很好的支持。

正如其他人所说,TotalView是这方面的标准。但是你要付出很大的代价。

OpenMPI 站点有一个很棒的 关于 MPI 调试的常见问题。FAQ 中的第6项描述了如何将 GDB 附加到 MPI 进程。读完整本书,里面有一些很棒的建议。

但是,如果您发现有太多的进程需要跟踪,请查看 堆栈跟踪分析工具(STAT)。在利弗莫尔,我们使用这种方法从潜在的成千上万个正在运行的进程中收集堆栈跟踪,并将它们智能地表示给用户。它不是一个功能齐全的调试器(功能齐全的调试器永远不会扩展到208k 内核) ,但它会告诉你哪些进程组正在做同样的事情。然后,可以在标准调试器中逐步通过每个组的代表。

Http://github.com/jimktrains/pgdb/tree/master 是我写来做这件事的一个实用程序。有一些文件,请随时向我提问。

您基本上调用了一个包装 GDB 的 perl 程序,并将它的 IO 传送到一个中央服务器。这允许 GDB 在每台主机上运行,并允许您在终端的每台主机上访问它。

我使用这个小小的自制方法将调试器附加到 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);
}

当然,您希望编译此函数仅用于调试构建。

我使用日志跟踪进行了一些与 MPI 相关的调试,但是如果使用 mpich2: MPICH2和 gdb,也可以运行 gdb。当您处理一个很难从调试器启动的进程时,这种技术通常是一种很好的实践。

还有我的开源工具 padb,它旨在帮助并行编程。我把它称为“作业检查工具”,因为它不仅可以作为一个调试器,也可以作为一个类似于并行顶部程序的功能。在“ Full Report”模式下运行,它将显示应用程序中每个进程的堆栈跟踪,以及每个级别上每个函数的本地变量(假设使用 -g 编译)。它还将显示“ MPI 消息队列”,即作业中每个等级的未完成发送和接收的列表。

除了显示完整的报告,还可以告诉 padb 放大作业中的单个信息位,还有无数的选项和配置项来控制显示什么信息,更多细节请参见网页。

帕德

我发现 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] [...]

将 gdb 附加到 mpi 进程的命令不完整,应该是

mpirun -np <NP> xterm -e gdb ./program

关于 mpi 和 gdb 的简要讨论可以在 给你中找到

正如其他人所提到的,如果您只使用 MPI 进程的 屈指可数,那么您可以尝试使用 多个 gdb 会话,这是一个令人敬畏的 瓦尔格林,或者使用您自己的 printf/log 解决方案。

如果您使用的进程比这多,那么您真的开始需要一个合适的调试器了。OpenMPI 常见问题解答推荐 Allinea DDTTotalView

我使用的是 Allinea DDT。它是一个功能齐全、图形化的源代码调试器,所以是的,你可以:

  • 调试或附加到(超过200k) MPI 进程
  • 步骤和暂停他们分组或单独
  • 添加断点、手表和跟踪点
  • 捕获内存错误和泄漏

等等,如果你用过 Eclipse 或者 Visual Studio,那么你就像在家里一样。

我们添加了一些有趣的特性,专门用于调试 平行代码(MPI、多线程或 CUDA) :

  • 标量变量在所有进程之间自动进行比较: Sparklines showing values across processes
    (来源: Allinea.com)

  • 您还可以跟踪和过滤变量和表达式在进程和时间上的值: Tracepoints log values over time

它广泛应用于 最高500高性能混凝土的各个位点,如 ORNLNCSALLNL尤利希等。

这个接口非常灵活; 我们计时单步执行并合并0.1秒的220,000个进程的堆栈和变量,作为在 Oak Ridge 的 Jaguar 集群上进行验收测试的一部分。

@ tgamblin 提到了与 Allinea DDT集成的优秀的 立刻,以及其他几个流行的开源项目。

这里的许多帖子都是关于 GDB 的,但是没有提到如何从启动开始附加到一个进程。显然,您可以附加到所有进程:

mpiexec -n X gdb ./a.out

但是这种方法非常无效,因为要启动所有流程,您必须四处反弹。如果只想调试一个(或少量) MPI 进程,可以使用 :操作符在命令行上将其作为单独的可执行文件添加:

mpiexec -n 1 gdb ./a.out : -n X-1 ./a.out

现在只有一个进程将获得 GDB。

使用 screengdb一起调试 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。然后,您可以对某些关键字(这里是字面 PID)的 stderr 文件 grep,以获取每个进程的 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 $MDRUN_EXE $PID将 gdb 会话附加到每个进程。在屏幕会话中这样做可以方便地访问任何 gdb 会话。-d -m以分离模式启动屏幕,-S "P$RANK"允许您为屏幕命名以便稍后访问,而 bash 的 -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

在屏幕上启动 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 会话,类似于前面的代码部分。

另一个解决方案是在 SMPI (模拟的 MPI)中运行代码。那是一个我参与的开源项目。每个 MPI 级别都将转换为相同 UNIX 进程的线程。然后,您可以轻松地使用 gdb 来逐步执行 MPI 等级。

SMPI 为 MPI 应用的研究提供了其他优势: 透视(你可以观察系统的每个部分) ,可重复性(除非你指定,否则几次运行会导致完全相同的行为) ,没有 Heisenbug (因为模拟平台与主机不同) ,等等。

有关更多信息,请参见 这个展示或该 相关的答案

如果你是一个 tmux用户,你会感到非常舒适使用 Benedikt Morbach: tmpi的脚本

来源: 翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳 https://github.com/moben/scripts/blob/master/tmpi

叉子: https://github.com/Azrael3000/tmpi

使用它可以实现多个面板(进程数量)的同步(每个命令同时复制到所有面板或进程上,因此与 xterm -e方法相比可以节省大量时间)。此外,你可以知道变量的值在进程中,你只需要做一个 print而不必移动到另一个面板,这将打印在每个面板的变量值为每个进程。

如果你不是一个 tmux用户,我强烈建议你试试看。

调试 MPI 程序的一种非常简单的方法。

在 main ()函数中添加 sleep (some _ second)

像往常一样运行程序

$ mpirun -np <num_of_proc> <prog> <prog_args>

程序将启动并进入睡眠状态。

因此,您将有一些时间通过 ps 查找进程,运行 gdb 并附加到它们。

如果您使用一些编辑器,如 QtCreator,您可以使用

调试-> 开始调试-> 附加到正在运行的应用程序

发现你在那里处理。

您可以使用可视化工作室代码,这是免费的,更容易与 xterm的工作。复制 VS Code 窗口并手动将调试器附加到每个进程。请看下面视频中的说明:

YouTube

我发布了 mpitx(S417-lama/mpitx) ,它的灵感来自于 tmpi中 tmux 的大量使用,但是它以最少的依赖性支持 多节点执行多节点执行(只支持 Python 3和 tmux)。

在(可能是分布式的)4个节点上启动 gdb,包括:

mpitx -n 4 -- gdb --args ./program arg1 arg2 ...

除了 tmpi,您还可以为每个 MPI 进程分配一个 tmux 窗格,并将键盘输入复制到所有 MPI 进程。 它确实有助于同时调试和检查多个节点上的计算机状态。

请检查 GitHub 网站查看工作示例。