2010-10-04 1 views
135

Я всегда удивлялся/разочаровался в том, сколько времени требуется, чтобы просто вывести на терминал оператор печати. После некоторых недавних болезненно медленных лесозаготовок я решил изучить это и был очень удивлен, обнаружив, что почти все потраченное время ждет, пока терминал обработает результаты.Почему печать на stdout так медленно? Можно ли его ускорить?

Может ли запись на stdout ускориться?

Я написал сценарий ('print_timer.py' в нижней части этого вопроса), чтобы сравнить время при записи строк 100k в stdout, в файл и с stdout, перенаправленным на /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

Ничего себе. Для того, чтобы убедиться, что питон не делать что-то за кулисами, как признание того, что я переназначить стандартный вывод в/DEV/нуль или что-то, что я сделал перенаправление вне сценария ...

$ 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?

This link говорит о том, как терминал будет блокировать I/O, чтобы он мог «разобрать [вход], обновить свой буфер кадров, обмен данными с сервером X для того, чтобы прокрутить окно и так далее» ... но я не полностью понял это. Что может занять так много времени?

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


UPDATE: после прочтения некоторых комментариев я задавался вопрос, сколько влияния моего размера экрана на самом деле имеет на время печати, и это имеет какое-то значение. Очень медленные цифры выше - мой терминал 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 

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

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

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

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


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

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 
+7

Вся цель написания на stdout - так человек может прочитать результат. Ни один человек в мире не может читать 10 000 строк текста за 12 секунд, так что в чем смысл ускорения работы stdout? –

+6

@Seun Osewa: Один пример (который вызвал мой вопрос) - это когда вы делаете такие вещи, как [отладка отписки печати] (http://stackoverflow.com/questions/189562). Вы хотите запустить свою программу и увидеть результаты по мере их возникновения. Вы, очевидно, правы, что большинство линий будут летать по тому, что вы не видите, но когда происходит исключение (или вы попадаете в условный оператор getch/raw_input/sleep, который вы тщательно размещаете), вы хотите смотреть прямо на печать, а не на постоянно открывать или обновлять вид файла. – Russ

+2

Отладка утверждения печати является одной из причин, по которым устройства tty (т.е. терминалы) по умолчанию используют буферизацию строк вместо буферизации блоков: вывод отладки не очень полезен, если программа зависает, а последние несколько строк вывода отладки все еще находятся в вместо сброса на терминал. –

ответ

71

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

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

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

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

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

Объяснение ...

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

 
----- 
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.26s НАМНОГО лучше, чем 12 с! Я не знаю, является ли wterm более разумным о том, как он отображает экран в соответствии с тем, как я предлагал (визуализировать «видимый» хвост с разумной частотой кадров), или просто «делает меньше», чем gnome-terminal. Однако для моего вопроса у меня есть ответ. gnome-terminal медленный.

Итак, если у вас длинный сценарий, который вы чувствуете медленно, и он извергает огромное количество текста на stdout ... попробуйте другой терминал и посмотрите, не лучше ли это!

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


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

 
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, очевидно.

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

+1

Вы также можете попробовать атерм. Вот результаты в моем тесте, используя ваш скрипт. Aterm - print: 0.491 s, записать в файл (+ fsync): 0.110 s, print with stdout =/dev/null: 0.087 s wterm - print: 0.521 s, записать в файл (+ fsync): 0.105 s , print with stdout =/dev/null: 0.085 s – frogstarr78

+0

@ frogstarr78: Спасибо за отзыв ... Я добавлю его в список. Я немного хуже, но достаточно близко. Во всяком случае, все номера для общего ощущения. – Russ

+1

Как urxvt сравнивается с rxvt? – Daenyth

1

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

11

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

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

Как забавный эксперимент, попробуйте выполнить вывод трубопровода на 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 
+0

Я не думал о проверке python на его выходе FS. Интересно, держит ли питон трюк за кулисами? Я не ожидаю, но не знаю. – Russ

+0

+1 для указания на существенную разницу в буферизации –

+0

@Russ: опция '-u' заставляет' stdin', 'stdout' и' stderr' быть небуферизованными, что будет медленнее, чем буферизация блоков (из-за накладные расходы) – Hasturkun

3

Я не могу говорить о технических деталях, потому что я не знаю их, но это меня не удивляет: терминал не был предназначен для печати больших объемов данных, как это. Действительно, вы даже предоставляете ссылку на загрузку GUI-файлов, которые она должна делать каждый раз, когда вы хотите что-то напечатать! Обратите внимание, что если вы вызываете скрипт с 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): 
    ... 
1

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

+0

True! Это побудило меня попытаться уменьшить размер окна терминала (в Gnome) до чего-то пустого (с 1920x1200). Конечно же ... 2.8s время печати против 11.5s. Гораздо лучше, но все же ... почему он заглох? Вы могли бы подумать, что буфер stdout (hmm) может обрабатывать все 100k строк, и дисплей терминала будет просто захватывать все, что он может поместить на экране из хвоста буфера, и сделать это за один быстрый выстрел. – Russ

+0

В этом случае xterm (или gterm) сделает ваш экран более быстрым, если он не подумает, что он должен был отобразить все остальные выходные данные на этом пути. Если бы попытаться пройти этот маршрут, скорее всего, общий случай небольших обновлений экрана будет менее отзывчивым. При написании этого типа программного обеспечения вы иногда можете иметь дело с ним, имея разные режимы и пытаясь обнаружить, когда вам нужно переходить в режим от малого до массового. Вы можете использовать 'cat big_file | хвост или даже 'cat big_file | tee big_file.cpy | хвост "очень часто для этой скорости. – nategoose

127

Как это может быть, что запись на физический диск является способ быстрее, чем запись на «экран» (предположительно оп все-RAM), а также эффективно, как быстро, как просто демпинг для мусора с/DEV/нуль ?

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

Диск появляется быть быстрее, так как она сильно буферном: весь 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 строк.

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

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

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

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

+4

Я получаю буферизацию ввода-вывода ... вы, конечно, напомнили мне, что мне нужно иметь fsync'd для истинного сравнения времени завершения (я обновляю вопрос), но fsync _per line_ - это безумие. Нужно ли действительно эффективно делать это? Нет ли буферизации терминалов/os-side, эквивалентной для файлов?т.е.: приложения записывают в stdout и возвращают до отображения терминала на экран, при этом терминал (или os) выполняет буферизацию всего. Затем терминал мог бы разумно отображать хвост на экране с видимой частотой кадров. Эффективное блокирование каждой строки кажется глупым. Я чувствую, что я все еще что-то упускаю. – Russ

+0

Вы можете просто открыть дескриптор stdout с помощью большого буфера, используя что-то вроде 'os.fdopen (sys.stdout.fileno(), 'w', BIGNUM)'. Это почти никогда не было бы полезно: почти все приложения должны были бы помнить, что они явно скрываются после каждой строки пользовательского вывода. –

+1

Я экспериментировал раньше с огромными (до 10 МБ с буферами на стороне python с 'fp = os.fdopen (sys .__ stdout __. Fileno(), 'w', 10000000)'). Воздействие было нулевым. т.е.: длительные задержки tty. Это заставило меня подумать/понять, что вы просто откладываете медленную проблему tty ... когда буфер python, наконец, сбрасывает tty, по-прежнему, похоже, делает то же самое общее количество обработки в потоке перед возвратом. – Russ

 Смежные вопросы

  • Нет связанных вопросов^_^