Pygraphviz 在绘制 170 个图后崩溃

Posted

技术标签:

【中文标题】Pygraphviz 在绘制 170 个图后崩溃【英文标题】:Pygraphviz crashes after drawing 170 graphs 【发布时间】:2020-07-07 15:19:45 【问题描述】:

我正在使用 pygraphviz 为不同的数据配置创建大量图表。我发现无论图表中放入什么信息,程序在绘制第 170 个图表后都会崩溃。没有生成错误消息,程序只是停止。画这么多图有什么需要重新设置的吗?

我在 Windows 10 机器上运行 Python 3.7、Pygraphviz 1.5 和 graphviz 2.38

    for graph_number in range(200):
        config_graph = pygraphviz.AGraph(strict=False, directed=False, compound=True, ranksep='0.2', nodesep='0.2')

        # Create Directory
        if not os.path.exists('Graph'):
            os.makedirs('Graph')

        # Draw Graph      
        print('draw_' + str(graph_number))
        config_graph.layout(prog = 'dot')
        config_graph.draw('Graph/'+str(graph_number)+'.png') 

【问题讨论】:

PyGraphviz 有一个带有问题选项卡的 github 源代码存储库 - 你试过在那里询问吗? 你有没有检查程序的内存使用/增加。也许内存 fre 不正确。您是否尝试将 for 语句直接移动到 # Draw Graph 之前?你在哪里加载了应该绘制的点代码? 补充一下@albert 所说的,如果您运行的是 32 位版本的 Python,内存问题似乎是最有可能出现的问题。但是,如果是这种情况,应该有一种方法可以减少内存使用量(这就是您问题的答案)。 @draB1 请检查 cmets 并提供反馈 【参考方案1】:

我能够通过以下方式不断重现该行为:

    Python 3.7.6 (pc064 (64bit),然后还有 pc032) PyGraphviz 1.5(我构建的 - 可在 [GitHub]: CristiFati/Prebuilt-Binaries - Various software built on various platforms. 下载(自然在 PyGraphviz 下) - 可能还想查看 [SO]: Installing pygraphviz on Windows 10 64-bit, Python 3.6 (@CristiFati's answer) ) Graphviz 2.42.2 ((pc032) 同 #2.)

我怀疑代码中某处存在未定义行为 (UB),即使该行为准确地一样的:

OK 169 图表 170 崩溃

做了一些调试(在 agraph.pycgraph.dll 中添加了一些 print(f) 语句(write. c))。 PyGraphviz 调用 Graphviz 的工具 (.exes) 进行许多操作。为此,它使用 subprocess.Popen 并通过其 3 个可用流(stdinstdoutstderr)。

从一开始我就注意到170 * 3 = 510(非常接近512 (0x200)),但直到后来我才注意到(主要是因为 Python 进程(运行下面的代码)任务管理器中打开的句柄不超过 ~150 个(TM) 以及 Process Explorer (PE))。

然而,一点 Google 透露:

[SO]: Is there a limit on number of open files in Windows (@stackprogrammer's answer)(从这里开始)

[MS.Docs]: _setmaxstdio(声明(重点是我的)):

C 运行时 I/O 现在支持在 low I/O level 同时打开多达 8,192 个文件。此级别包括使用 _open_read_write 系列 I/O 函数打开和访问的文件。默认情况下,stream I/O level 最多可以同时打开 512 个文件。此级别包括使用 fopenfgetcfputc 系列函数打开和访问的文件。 512 在流 I/O 级别打开文件的限制可以增加到最大 8,192 _setmaxstdio 函数的使用。

[SO]: Python: Which command increases the number of open files on Windows? (@NorthCat's answer)

以下是我为调试和重现错误而修改的代码。它需要(为了代码简洁,同样的事情可以通过 CTypes 实现)PyWin32 包 (python -m pip install pywin32)。

code00.py

#!/usr/bin/env python

import sys
import os
#import time
import pygraphviz as pgv
import win32file as wfile


def handle_graph(idx, dir_name):
    graph_name = "draw_0:03d".format(idx)
    graph_args = 
        "name": graph_name,
        "strict": False,
        "directed": False,
        "compound": True,
        "ranksep": "0.2",
        "nodesep": "0.2",
    
    graph = pgv.AGraph(**graph_args)
    # Draw Graph      
    img_base_name = graph_name + ".png"
    print("  0:s".format(img_base_name))
    graph.layout(prog="dot")
    img_full_name = os.path.join(dir_name, img_base_name)
    graph.draw(img_full_name)
    graph.close()  # !!! Has NO (visible) effect, but I think it should be called anyway !!!


def main(*argv):

    print("OLD max open files: 0:d".format(wfile._getmaxstdio()))
    # 513 is enough for your original code (170 graphs), but you can set it up to 8192
    wfile._setmaxstdio(513)  # !!! COMMENT this line to reproduce the crash !!!
    print("NEW max open files: 0:d".format(wfile._getmaxstdio()))

    dir_name = "Graph"
    # Create Directory
    if not os.path.isdir(dir_name):
        os.makedirs(dir_name)

    #ts_global_start = time.time()
    start = 0
    count = 169
    #count = 1
    step_sleep = 0.05
    for i in range(start, start + count):
        #ts_local_start = time.time()
        handle_graph(i, dir_name)
        #print("  Time: 0:.3f".format(time.time() - ts_local_start))
        #time.sleep(step_sleep)
    handle_graph(count, dir_name)
    #print("Global time: 0:.3f".format(time.time() - ts_global_start - step_sleep * count))


if __name__ == "__main__":
    print("Python 0:s 1:dbit on 2:s\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main(*sys.argv[1:])
    print("\nDone.")

输出

e:\Work\Dev\***\q060876623>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32

OLD max open files: 512
NEW max open files: 513
  draw_000.png
  draw_001.png
  draw_002.png

...

  draw_167.png
  draw_168.png
  draw_169.png

Done.

结论

显然,一些文件句柄 (fds) 是打开的,尽管 TMPE 没有“看到”它们(可能它们处于较低水平)。但是我不知道为什么会发生这种情况(它是 MS UCRT 错误吗?),但就我而言,一旦子进程结束,它的流应该被关闭,但我不知道如何强制它(这将是一个适当的修复) 此外,尝试写入未打开)到fd(超过限制),似乎有点奇怪 作为一种解决方法,可以增加 max 打开 fd 的数量。根据以下不等式:3 * (graph_count + 1) <= max_fds,您可以对数字有所了解。从那里开始,如果您将限制设置为 8192(我没有对此进行测试),您应该能够处理 2729 图表(假设有代码没有打开额外的fd

旁注

在调查过程中,我遇到或注意到了几个相邻的问题,我试图解决这些问题:

图形化: [GitLab]: graphviz/graphviz - [Issue #1481]: MSB4018 The NativeCodeAnalysis task failed unexpectedly.(合并于 20200406PyGraphviz: [GitHub]: pygraphviz/pygraphviz - AGraph Graphviz handle close mechanism

此行为也存在一个问题(可能是同一作者):[GitHub]: pygraphviz/pygraphviz - Pygraphviz crashes after drawing 170 graphs

【讨论】:

这是一部令人印象深刻的侦探作品。如果 OP 直接不使用 调用subprocess.run 会发生什么使用标准流,即设置为DEVNULL 的流? @RolandSmith:谢谢。调用的是 PyGraphviz。我尝试修改close_fds=True,但没有任何效果。不确定 OP 如何在不修改 pgv 代码的情况下做某事。但即使这样,它也不会起作用,因为子进程需要与之交互,而 subprocess 高级包装器不允许这样做。 我的意思是没有使用 PyGraphviz。 出色的侦探作品!让我们确保这最终出现在相应的回购问题中❤️ @DimaTisnek:谢谢。显然有一个 PyGraphviz 错误:github.com/pygraphviz/pygraphviz/issues/213.【参考方案2】:

我尝试了你的代码,它生成了 200 个没有问题的图表(我也尝试了 2000 个)。

我的建议是使用这些版本的软件包,我在 mac os 上使用 python 3.7 安装了 conda 环境:

graphviz 2.40.1 hefbbd9a_2

pygraphviz 1.3 py37h1de35cc_1

【讨论】:

我认为这不能提供真正的答案,因为您在 Mac 上工作并在 Windows 上使用 OP。此外,您使用了不同版本的 graphviz(较新)和 pygraphviz(较旧)。问题可能与系统有关。 我同意这并不能解释 OP 会发生什么。但这给出了代码没有问题的想法,没有执行重置(这是 OP 问题)。此外,使用这些版本也可以为 windows 提供真正的解决方案。 OP 代码可能没有错,但可能有一些问题:pygrapviz 1.5 或使用的python 版本(3.7 是通用名称,可能是子 3.7.x 问题),可能是内存泄漏/垃圾收集问题......但直到 OP 回答这些问题我们才会知道。 同意。我仍然认为我的回答可能会提供一个值得尝试的解决方案。

以上是关于Pygraphviz 在绘制 170 个图后崩溃的主要内容,如果未能解决你的问题,请参考以下文章

MAC 安装 pygraphviz 找不到头文件

pygraphviz OSError:格式:“点”无法识别。使用以下之一:

如何使用cowplot包在R中的同一页面上制作几个图?

几个图在flot中具有相同的图例

在绘制条形图时尝试更改数字格式

在 Windows 上安装 pygraphviz