Pygraphviz падает после рисования 170 графиков - PullRequest
5 голосов
/ 27 марта 2020

Я использую pygraphviz для создания большого количества графиков для разных конфигураций данных. Я обнаружил, что независимо от того, какая информация помещена в график, программа будет рисовать sh после построения 170-го графика. Нет сгенерированных сообщений об ошибках, программа просто останавливается. Есть ли что-то, что должно быть сброшено при рисовании такого количества графиков?

Я работаю Python 3.7 на машине Windows 10, 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') 

Ответы [ 2 ]

5 голосов
/ 06 апреля 2020

Мне удалось постоянно воспроизвести поведение с:

  1. Python 3.7.6 ( pc064 ( 64bit ), затем также с pc032 )
  2. PyGraphviz 1.5 (который я создал - доступен для загрузки на [GitHub]: CristiFati / Prebuilt- Двоичные файлы - различное программное обеспечение, созданное на различных платформах. (под PyGraphviz , естественно) - может также потребоваться проверка [SO]: установка pygraphviz на Windows 10 64-бит , Python 3,6 (@ ответ CristiFati) )
  3. Graphviz 2.42.2 (( pc032 ) аналогично # 2 . )

Я подозревал неопределенное поведение ( UB ) где-то в коде, даже если поведение было точно то же самое:

  • ОК для 169 графики
  • Cra sh для 170

Сделал некоторую отладку (добавил немного print (f) * 10 64 * операторов в agraph.py и cgraph.dll ( write. c)).
PyGraphviz вызывает Graphviz инструментов ( .exe s) для многих операций. Для этого он использует subprocess.Popen и связывается с дочерним процессом через 3 доступных потока ( stdin , stdout , stderr ) .

С самого начала я заметил, что 170 * 3 = 510 (очень близко к 512 ( 0x200 )), но не обратил столько внимания, сколько следовало бы до позже (в основном потому, что процесс Python (с кодом ниже) имел не более ~ 150 открытых дескрипторов в диспетчере задач ( TM ), а также Process Explorer ( PE ) ).

Однако, немного Google ing обнаружено:

Ниже приведен ваш код, который я изменил для отладки и воспроизведения ошибки. Для этого требуется пакет 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:d}bit 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\StackOverflow\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.

Выводы :

  • Очевидно, некоторые файловые дескрипторы ( fd s) открыты, хотя они не «видны» TM или PE (вероятно, они находятся на более низком уровне). Однако я не знаю, почему это происходит (это ошибка MS UCRT ?), Но, с моей точки зрения, после завершения дочернего процесса его потоки должны быть закрыты, но я не знаю как заставить его ( это было бы правильным исправлением )
  • Кроме того, поведение ( cra sh) при попытке записи ( не открыто ) для fd (выше предела), кажется немного странным
  • В качестве обходного пути, max open FD S число может быть увеличено. Основываясь на следующем неравенстве: 3 * (graph_count + 1) <= max_fds, вы можете получить представление о числах. Оттуда, если вы установите ограничение на 8192 (я не проверял это), вы сможете обрабатывать 2729 графиков (при условии, что нет никаких дополнительных fd s открывается кодом)

Примечания :

0 голосов
/ 30 марта 2020

Я попробовал ваш код, и он без проблем сгенерировал 200 графиков (я тоже пробовал с 2000).

Я предлагаю использовать эти версии пакетов, я установил среду conda на ma c ОС с python 3.7:

graphviz 2.40.1 hefbbd9a_2

pygraphviz 1.3 py37h1de35cc_1

...