Почему печать на стандартный вывод так медленно? Это может быть ускорено? - PullRequest
158 голосов
/ 04 октября 2010

Я всегда удивлялся / расстраивался из-за того, сколько времени требуется, чтобы просто вывести на терминал оператор print.После недавней мучительно медленной регистрации я решил посмотреть на нее и с удивлением обнаружил, что почти все затраченного времени ожидает, пока терминал обработает результаты.

Может записать в stdoutкак-нибудь ускориться?

Я написал скрипт ('print_timer.py' внизу этого вопроса), чтобы сравнить время при записи 100 тыс. строк в стандартный вывод, в файл и с перенаправленным на стандартный вывод /dev/null.Вот результат синхронизации:

$ python print_timer.py
this is a test
this is a test
<snipped 99997 lines>
this is a test
-----
timing summary (100k lines each)
-----
print                         :11.950 s
write to file (+ fsync)       : 0.122 s
print with stdout = /dev/null : 0.050 s

Вау.Чтобы убедиться, что python не делает что-то за кулисами, например, узнав, что я переназначил stdout на / dev / null или что-то еще, я сделал перенаправление вне скрипта ...

$ python print_timer.py > /dev/null
-----
timing summary (100k lines each)
-----
print                         : 0.053 s
write to file (+fsync)        : 0.108 s
print with stdout = /dev/null : 0.045 s

Так что это не тактрюк с питоном, это просто терминал.Я всегда знал, что вывод данных в / dev / null ускорял процесс, но никогда не думал, что это так важно!

Меня поражает, насколько медленным является tty.Как может быть так, что запись на физический диск является ПУТЬ быстрее, чем запись на «экран» (предположительно операционная система все-ОЗУ), и эффективна так же быстро, как простой вывод в мусор с / dev / null?

Эта ссылка говорит о том, как терминал заблокирует ввод / вывод, чтобы он мог "проанализировать [вход], обновить свой буфер кадров, связаться с X-сервером для прокрутки окна и т. Д.на " ... но я не до конца понимаю.Что может занять так много времени?

Я ожидаю, что нет никакого выхода (если не считать более быстрой реализации tty?), Но я бы все-таки попросил цифру.


ОБНОВЛЕНИЕ: после прочтениянекоторые комментарии Я задавался вопросом, как сильно влияет размер моего экрана на время печати, и это имеет некоторое значение.Очень медленные цифры выше - мой терминал Gnome взорван до 1920x1200.Если я уменьшу его очень мало, я получу ...

-----
timing summary (100k lines each)
-----
print                         : 2.920 s
write to file (+fsync)        : 0.121 s
print with stdout = /dev/null : 0.048 s

Это, конечно, лучше (~ 4 раза), но не меняет моего вопроса.Это только добавляет к моему вопросу, так как я не понимаю, почему рендеринг экрана терминала должен замедлять запись приложения в стандартный вывод.Почему моей программе нужно ждать продолжения рендеринга экрана?

Все ли терминальные / tty-приложения не созданы равными?Я еще не экспериментировал.Мне действительно кажется, что терминал должен иметь возможность буферизовать все входящие данные, анализировать / визуализировать их невидимым образом и отображать только самый последний фрагмент, видимый в текущей конфигурации экрана, с разумной частотой кадров.Поэтому, если я смогу записать + fsync на диск за ~ 0,1 секунды, терминал должен быть в состоянии выполнить ту же операцию в каком-то порядке (возможно, с несколькими обновлениями экрана, пока он это делал).

I 'Я все еще надеюсь, что есть параметр tty, который можно изменить со стороны приложения, чтобы сделать это лучше для программиста.Если это строго проблема с терминальным приложением, то, возможно, это даже не относится к StackOverflow?

Что мне не хватает?


Вот программа на python, используемая для генерации времени:

import time, sys, tty
import os

lineCount = 100000
line = "this is a test"
summary = ""

cmd = "print"
startTime_s = time.time()
for x in range(lineCount):
    print line
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

#Add a newline to match line outputs above...
line += "\n"

cmd = "write to file (+fsync)"
fp = file("out.txt", "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
os.fsync(fp.fileno())
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

cmd = "print with stdout = /dev/null"
sys.stdout = file(os.devnull, "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

print >> sys.stderr, "-----"
print >> sys.stderr, "timing summary (100k lines each)"
print >> sys.stderr, "-----"
print >> sys.stderr, summary

Ответы [ 6 ]

145 голосов
/ 04 октября 2010

Как может быть так, что запись на физический диск является ПУТЬ быстрее, чем запись на «экран» (предположительно операционная система все-ОЗУ), и эффективно так же быстро, как простой вывод в мусор с / dev / null?

Поздравляем, вы только что обнаружили важность буферизации ввода / вывода. : -)

Диск выглядит более быстрым, потому что он сильно буферизован: все вызовы Python write() возвращаются до того, как что-либо действительно будет записано на физический диск. (ОС делает это позже, объединяя многие тысячи отдельных записей в большие эффективные блоки.)

Терминал, с другой стороны, выполняет небольшую буферизацию или не выполняет ее вовсе: каждый отдельный print / write(line) ожидает завершения полной записи (т. Е. Отображения на устройство вывода).

Чтобы сделать сравнение справедливым, вы должны сделать так, чтобы тест файла использовал ту же буферизацию вывода, что и терминал, что вы можете сделать, изменив свой пример на:

fp = file("out.txt", "w", 1)   # line-buffered, like stdout
[...]
for x in range(lineCount):
    fp.write(line)
    os.fsync(fp.fileno())      # wait for the write to actually complete

Я запустил ваш тест записи файлов на моем компьютере, и с буферизацией он также составил 0,05 с для 100 000 строк.

Однако с учетом указанных выше модификаций для записи без буферизации для записи только 1000 строк на диск требуется 40 секунд. Я перестал ждать записи 100 000 строк, но, экстраполируя предыдущие, это заняло бы в течение часа .

Это переводит 11 секунд терминала в перспективу, не так ли?

Таким образом, чтобы ответить на ваш первоначальный вопрос, запись в терминал на самом деле невероятно быстрая, учитывая все обстоятельства, и не так много места, чтобы сделать это намного быстрее (но отдельные терминалы различаются в зависимости от того, насколько они работают; см. Расс комментарий к этому ответу).

(Вы могли бы добавить больше буферизации записи, как с дисковым вводом / выводом, но тогда вы не увидите, что было записано на ваш терминал, пока буфер не будет очищен. Это компромисс: интерактивность и массовая эффективность.)

83 голосов
/ 05 октября 2010

Спасибо за все комментарии! Я закончил тем, что отвечал на это сам с вашей помощью. Чувствую себя грязно, когда отвечаешь на твой вопрос.

Вопрос 1: Почему медленная печать на стандартный вывод?

Ответ: Печать на стандартный вывод не по своей сути медленная. Это терминал, с которым ты работаешь медленно. И это в значительной степени нулевое отношение к буферизации ввода / вывода на стороне приложения (например, буферизация файла Python). Смотри ниже.

Вопрос 2: Можно ли его ускорить?

Ответ: Да, может, но, по-видимому, не со стороны программы (со стороны, выполняющей «печать» на стандартный вывод). Чтобы ускорить его, используйте другой эмулятор терминала.

Пояснение ...

Я попытался описать собственную «легковесную» терминальную программу под названием wterm и получил значительно лучших результатов. Ниже приведен вывод моего тестового сценария (в нижней части вопроса) при запуске в wterm при 1920x1200 в в той же системе, где базовая опция печати заняла 12 с использованием gnome-терминала:

-----
timing summary (100k lines each)
-----
print                         : 0.261 s
write to file (+fsync)        : 0.110 s
print with stdout = /dev/null : 0.050 s

0,26 с намного лучше, чем 12 с! Я не знаю, является ли wterm более умным в отношении того, как он отображает в соответствии с тем, как я предлагал (рендеринг «видимого» хвоста с разумной частотой кадров), или он просто «делает меньше», чем 1031 *. Для целей моего вопроса у меня есть ответ, хотя. gnome-terminal медленно.

Итак - если у вас есть долго работающий скрипт, который вы считаете медленным, и он изливает огромное количество текста на стандартный вывод ... попробуйте другой терминал и посмотрите, будет ли он лучше!

Обратите внимание, что я почти случайно вытащил wterm из репозиториев Ubuntu / Debian. Эта ссылка может быть тем же терминалом, но я не уверен. Я не тестировал другие эмуляторы терминала.


Обновление: поскольку мне пришлось чесать зуд, я протестировал целую кучу других эмуляторов терминала с тем же сценарием и полноэкранным режимом (1920x1200). Моя вручную собранная статистика здесь:

wterm           0.3s
aterm           0.3s
rxvt            0.3s
mrxvt           0.4s
konsole         0.6s
yakuake         0.7s
lxterminal        7s
xterm             9s
gnome-terminal   12s
xfce4-terminal   12s
vala-terminal    18s
xvt              48s

Записанные времена собираются вручную, но они были довольно последовательными. Я записал лучшее (ish) значение. YMMV, очевидно.

В качестве бонуса это был интересный тур по различным эмуляторам терминалов, доступных там! Я поражен, что мой первый «альтернативный» тест оказался лучшим из всех.

13 голосов
/ 04 октября 2010

Возможно, ваше перенаправление ничего не делает, так как программы могут определить, указывает ли их выходной FD на tty.

Вероятно, что stdout буферизуется при указании на терминал (аналогично C * stdout поведение потока).

В качестве забавного эксперимента попробуйте вывести вывод на cat.


Я попробовал свой забавный эксперимент, и вот результаты.

$ python test.py 2>foo
...
$ cat foo
-----
timing summary (100k lines each)
-----
print                         : 6.040 s
write to file                 : 0.122 s
print with stdout = /dev/null : 0.121 s

$ python test.py 2>foo |cat
...
$ cat foo
-----
timing summary (100k lines each)
-----
print                         : 1.024 s
write to file                 : 0.131 s
print with stdout = /dev/null : 0.122 s
4 голосов
/ 04 октября 2010

Я не могу говорить о технических деталях, потому что я их не знаю, но меня это не удивляет: терминал не был предназначен для печати большого количества подобных данных. Действительно, вы даже предоставляете ссылку на множество графических элементов, которые он должен делать каждый раз, когда вы хотите что-то напечатать! Обратите внимание: если вы вызываете скрипт с pythonw, это не займет 15 секунд; это полностью проблема с графическим интерфейсом. Чтобы избежать этого, перенаправьте stdout в файл:

import contextlib, io
@contextlib.contextmanager
def redirect_stdout(stream):
    import sys
    sys.stdout = stream
    yield
    sys.stdout = sys.__stdout__

output = io.StringIO
with redirect_stdout(output):
    ...
3 голосов
/ 04 октября 2010

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

2 голосов
/ 04 октября 2010

В дополнение к выходу, который по умолчанию установлен в режим линейного буфера, вывод на терминал также приводит к тому, что ваши данные поступают в терминал и последовательную линию с максимальной пропускной способностью, или в псевдотерминал и в отдельный процесс, которыйобработка цикла событий отображения, рендеринг символов из некоторого шрифта, перемещение битов отображения для реализации прокрутки дисплея.Последний сценарий, вероятно, распространяется на несколько процессов (например, telnet-сервер / клиент, терминальное приложение, сервер отображения X11), поэтому возникают проблемы с переключением контекста и задержкой.

...