Python "print"嵌入到 MPI 程序中时不起作用

Python "print" not working when embedded into MPI program

本文关键字:程序 不起作用 MPI print Python      更新时间:2023-10-16

我在C++MPI应用程序中嵌入了一个Python 3解释器。此应用程序加载一个脚本并将其传递给解释器。

当我在没有MPI启动器的情况下(只需调用./myprogram)在1个进程上执行程序时,脚本会正确执行,其"打印"语句会输出到终端。当脚本出现错误时,我会使用PyErr_print()在C++端打印它。

然而,当我通过mpirun启动程序时(即使是在单个进程上),我也不会从python代码中的"print"获得任何输出。当我的脚本出现错误时,我也不会从PyErr_Print()中得到任何东西。

我想Python处理标准输出的方式与MPI(此处为actall-Mpich)处理将进程的输出重定向到启动器并最终重定向到终端的方式不匹配。

你知道怎么解决这个问题吗?

[编辑,遵循本期建议]

每次调用PyErr_Print后,您需要flush_io(),其中flush_io可以是以下函数:

void flush_io(void)
{
  PyObject *type, *value, *traceback;
  PyErr_Fetch(&type, &value, &traceback);  // in Python/pythonrun.c, they save the traceback, let's do the the same
  for (auto& s: {"stdout", "stderr"}) {
    PyObject *f = PySys_GetObject(s);
    if (f)  PyObject_CallMethod(f, "flush", NULL);
    else    PyErr_Clear();
  }
  
  PyErr_Restore(type, value, traceback);
}

[在我的旧分析下面,它仍然有一些有趣的信息]

我最终遇到了同样的问题(PyErr_Print无法从mpirun中运行)。追溯(涉及python3的一些gdb),并比较工作的东西(./myprogram)和不工作的事情(mpirun-np 1./myprogram),我最终在./Modules/_io/textio.c:1277_io_TextIOWrapper_write_impl(顺便说一句,python-3.60)。

两次运行之间的唯一区别是self->line_buffering是1对0(此时self表示sys.stderr)。然后,在pylifecycle.c:1128中,我们可以看到是谁决定了这个值:

if (isatty || Py_UnbufferedStdioFlag)
    line_buffering = Py_True;

因此,MPI似乎在启动程序之前对stderr做了一些事情,这使得它不是tty。我还没有调查mpirun中是否有将tty标志保留在stderr上的选项。。。如果有人知道的话,这会很有趣(尽管经过仔细考虑,mpi可能有充分的理由将他的文件描述符放在stdout&stderr的位置,例如它的--output文件名)。

有了这些信息,我可以提出3个解决方案(前两个是快速修复,第三个更好):

1/在启动python解释器的C代码中,在创建sys.stderr之前设置缓冲标志

Py_UnbufferedStdioFlag = 1;   // force line_buffering for _all_ I/O
Py_Initialize(); 

这使得Python的回溯在所有情况下都能回到屏幕;但可能会带来灾难性的I/O。。。因此只有在调试模式下才是可接受的解决方案。

2/在python(嵌入式)脚本中,在最开始添加以下内容:

import sys
#sys.stderr.line_buffering = True  # would be nice, but readonly attribute !
sys.stderr = open("error.log", 'w', buffering=1 )

然后,脚本将跟踪转储到此文件error.log。

我还尝试在PyErr_Print()之后添加一个对fflush(stderr)或ffrush(NULL)的调用。。。但这并没有起作用(因为sys.stderr有自己的内部缓冲)。不过,这将是一个不错的解决方案。

3/经过进一步的挖掘,我发现中的完美功能

Python/pythonrun.c:57:static void flush_io(void);

事实上,它是在该文件中的每个PyErr_Print之后调用的。不幸的是,它是静态的(只存在于该文件中,在Python.h中没有引用它,至少在3.6.0中是这样)。我将该文件中的函数复制到myprogram中,结果它确实完成了任务。