Модуль подпроцесса Python намного медленнее команд (устарел)

Итак, я написал script, который обращается к кучке серверов, использующих nc в командной строке, и изначально я использовал модуль команд Python, а вызовы команд .getoutput() и script выполнялись примерно через 45 секунд. Поскольку команды устарели, я хочу изменить все на использование модуля подпроцесса, но теперь script выполняет 2m45s для запуска. У кого-нибудь есть идея, почему это было бы?

То, что у меня было до:

output = commands.getoutput("echo get file.ext | nc -w 1 server.com port_num")

теперь у меня

p = Popen('echo get file.ext | nc -w 1 server.com port_num', shell=True, stdout=PIPE)
output = p.communicate()[0]

Заранее благодарим за помощь!

Ответ 1

Я ожидаю, что subprocess будет медленнее, чем command. Без смысла предположить, что это единственная причина, по которой ваш script работает медленно, вы должны взглянуть на commands исходный код, Менее 100 строк, и большая часть работы делегируется функциям от os, многие из которых берутся прямо из библиотек c posix (по крайней мере, в posix-системах). Обратите внимание, что commands является unix-only, поэтому ему не нужно выполнять какую-либо дополнительную работу для обеспечения совместимости между платформами.

Теперь взгляните на subprocess. Есть более 1500 строк, все чистые Python, делая всевозможные проверки для обеспечения согласованного кросс-платформенного поведения. Исходя из этого, я ожидал бы, что subprocess будет работать медленнее, чем commands.

Я приурочил два модуля, и на чем-то довольно базовом, subprocess был почти в два раза медленнее, чем commands.

>>> %timeit commands.getoutput('echo "foo" | cat')
100 loops, best of 3: 3.02 ms per loop
>>> %timeit subprocess.check_output('echo "foo" | cat', shell=True)
100 loops, best of 3: 5.76 ms per loop

Swiss предлагает некоторые хорошие улучшения, которые помогут вашей производительности script. Но даже после их применения обратите внимание, что subprocess все еще медленнее.

>>> %timeit commands.getoutput('echo "foo" | cat')
100 loops, best of 3: 2.97 ms per loop
>>> %timeit Popen('cat', stdin=PIPE, stdout=PIPE).communicate('foo')[0]
100 loops, best of 3: 4.15 ms per loop

Предполагая, что вы выполняете указанную выше команду много раз подряд, это добавит и учтет хотя бы часть разницы в производительности.

В любом случае, я интерпретирую ваш вопрос как относительную производительность subprocess и command, а не как ускорить ваш script. Для последнего вопроса лучше ответить на швейцарский.

Ответ 2

Здесь, по-видимому, есть как минимум два отдельных вопроса.

Во-первых, вы неправильно используете Popen. Вот проблемы, которые я вижу:

  • Истерирование нескольких процессов с помощью одного Popen.
  • Передача одной строки в качестве аргументов вместо разделения аргументов.
  • Использование оболочки для передачи текста для обработки, а не встроенного метода связи.
  • Использование оболочки, а не непосредственно процессов нереста.

Вот скорректированная версия вашего кода

from subprocess import PIPE

args = ['nc', '-w', '1', 'server.com', 'port_num']
p = subprocess.Popen(args, stdin=PIPE, stdout=PIPE)
output = p.communicate("get file.ext")
print output[0]

Во-вторых, тот факт, что вы предлагаете его быстрее работать при запуске вручную, чем при прохождении через подпроцесс, предполагает, что проблема заключается в том, что вы не передаете правильную строку в nc. Вероятно, происходит то, что сервер ждет окончания строки для завершения соединения. Если вы не пропустите это, соединение, вероятно, останется открытым до истечения времени.

Запустите nc вручную, выясните, что такое завершающая строка, затем обновите строку, переданную в communicate. С этими изменениями он должен работать намного быстрее.