Я всегда был поражен / расстроен тем, сколько времени требуется, чтобы просто вывести на терминал оператор 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 секунды, терминал должен быть в состоянии выполнить ту же операцию в каком-то порядке (возможно, с несколькими обновлениями экрана, пока он это делал).
Я все еще надеюсь, что есть параметр 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
Ответы:
Поздравляем, вы только что обнаружили важность буферизации ввода / вывода. :-)
Диск кажется более быстрым, потому что он сильно буферизован: все
write()
вызовы Python возвращаются до того, как что-то будет записано на физический диск. (ОС делает это позже, объединяя многие тысячи отдельных записей в большие, эффективные блоки.)Терминал, с другой стороны, выполняет небольшую буферизацию или не выполняет ее вовсе: каждый индивид
print
/write(line)
ожидает завершения полной записи (т.е. отображения на устройство вывода).Чтобы сделать сравнение справедливым, вы должны заставить файловый тест использовать ту же буферизацию вывода, что и терминал, что вы можете сделать, изменив свой пример на:
Я запустил ваш тест записи файлов на моей машине, и с буферизацией он также составил 0,05 с здесь для 100 000 строк.
Однако с учетом указанных выше модификаций для записи без буферизации для записи только 1000 строк на диск требуется 40 секунд. Я перестал ждать записи 100 000 строк, но экстраполяция из предыдущего заняла бы больше часа .
Это ставит в перспективу 11 секунд терминала, не так ли?
Таким образом, чтобы ответить на ваш первоначальный вопрос, запись в терминал на самом деле невероятно быстрая, учитывая все обстоятельства, и не так много места, чтобы сделать это намного быстрее (но отдельные терминалы различаются по тому, как много работы они выполняют; см. Комментарий Русса к этому). ответ).
(Вы можете добавить дополнительную буферизацию записи, как с дисковым вводом / выводом, но тогда вы не увидите, что было записано на ваш терминал, пока буфер не будет очищен. Это компромисс: интерактивность по сравнению с общей эффективностью.)
источник
os.fdopen(sys.stdout.fileno(), 'w', BIGNUM)
. Это почти никогда не будет полезно: почти все приложения должны будут помнить, что нужно явно сбрасывать после каждой строки вывода, предназначенного для пользователя.fp = os.fdopen(sys.__stdout__.fileno(), 'w', 10000000)
) буферами на стороне Python. Влияние было ноль. то есть: все еще долгие задержки tty. Это заставило меня подумать / осознать, что вы просто откладываете медленную проблему tty ... когда буфер python, наконец, сбрасывает tty, кажется, все еще выполняет тот же общий объем обработки потока перед возвратом.fdopen
буфер (2 МБ) определенно имел огромное значение: время печати сократилось со многих секунд до 0,05 с, так же как и вывод файла (использованиеgnome-terminal
).Спасибо за все комментарии! Я закончил тем, что отвечал на это сам с вашей помощью. Чувствую себя грязно, когда отвечаешь на твой вопрос.
Вопрос 1: Почему медленная печать на стандартный вывод?
Ответ. Печать на стандартный вывод не является медленной. Это терминал, с которым ты работаешь медленно. И он практически не связан с буферизацией ввода-вывода на стороне приложения (например, буферизация файла python). Увидеть ниже.
Вопрос 2: это может быть ускорено?
Ответ: Да, это возможно, но, по-видимому, не со стороны программы (со стороны, выполняющей «печать» на стандартный вывод). Чтобы ускорить его, используйте другой эмулятор терминала.
Объяснение ...
Я попробовал так называемую «легковесную» терминальную программу
wterm
и получил значительно лучшие результаты. Ниже приведен вывод моего тестового сценария (внизу вопроса) при работе вwterm
разрешении 1920x1200 в той же системе, где базовая опция печати заняла 12 с использованием gnome-терминала:0,26 с намного лучше, чем 12 с! Я не знаю,
wterm
является ли он более умным в отношении того, как он отображает в соответствии с тем, как я предлагал (визуализировать «видимый» хвост с разумной частотой кадров), или он просто «делает меньше», чемgnome-terminal
. Для целей моего вопроса у меня есть ответ, хотя.gnome-terminal
медленный.Итак, если у вас есть долго работающий скрипт, который вы считаете медленным, и он изливает огромное количество текста на стандартный вывод ... попробуйте другой терминал и посмотрите, будет ли он лучше!
Обратите внимание, что я почти случайно вытащил
wterm
из репозиториев Ubuntu / Debian. Эта ссылка может быть тем же терминалом, но я не уверен. Я не тестировал другие эмуляторы терминала.Обновление: поскольку мне пришлось почесать зуд, я протестировал целую кучу других эмуляторов терминала с тем же сценарием и полноэкранным режимом (1920x1200). Моя вручную собранная статистика здесь:
Записанные времена собираются вручную, но они были довольно последовательными. Я записал лучшее (ish) значение. YMMV, очевидно.
В качестве бонуса это был интересный тур по различным эмуляторам терминала, доступным там! Я поражен, что мой первый «альтернативный» тест оказался лучшим из всех.
источник
screen
(программа) должна быть включена в список! (Илиbyobu
, который является оберткой дляscreen
расширений). Эта утилита позволяет иметь несколько терминалов, так же, как вкладки в X-терминалах. Я предполагаю, что печать на текущемscreen
терминале аналогична печати на обычном терминале, но как насчет печати на одном изscreen
терминалов и последующего переключения на другой без активности?print: 0.587 s, write to file (+fsync): 0.034 s, print with stdout = /dev/null : 0.041 s
. И с «экраном», запущенным в iTerm2:print: 1.286 s, write to file (+fsync): 0.043 s, print with stdout = /dev/null : 0.033 s
Ваше перенаправление, вероятно, ничего не делает, так как программы могут определить, указывает ли их выходной FD на tty.
Вероятно, что stdout буферизуется при указании на терминал (то же самое, что и
stdout
поведение потока C ).В качестве забавного эксперимента попробуйте передать вывод
cat
.Я попробовал свой забавный эксперимент, и вот результаты.
источник
-u
опционные силыstdin
,stdout
иstderr
быть небуферизован, который будет медленнее , чем быть блок - буферном (из - за накладных расходов)Я не могу говорить о технических деталях, потому что я их не знаю, но меня это не удивляет: терминал не был предназначен для печати большого количества подобных данных. Действительно, вы даже предоставляете ссылку на множество графических элементов, которые он должен делать каждый раз, когда вы хотите что-то напечатать! Обратите внимание, что если вы вызываете скрипт
pythonw
вместо этого, это не займет 15 секунд; это полностью проблема с графическим интерфейсом. Перенаправитьstdout
в файл, чтобы избежать этого:источник
Печать в терминал будет медленной. К сожалению, кроме написания новой реализации терминала, я не могу понять, как вы значительно ускорите это.
источник
В дополнение к выводу, который, вероятно, по умолчанию использует режим линейной буферизации, вывод на терминал также заставляет ваши данные поступать в терминальную и последовательную линию с максимальной пропускной способностью или в псевдотерминал и отдельный процесс, который обрабатывает отображение цикл обработки событий, рендеринг символов из некоторого шрифта, перемещение битов дисплея для реализации прокрутки дисплея. Последний сценарий, вероятно, распространяется на несколько процессов (например, telnet-сервер / клиент, приложение терминала, сервер отображения X11), поэтому существуют проблемы переключения контекста и задержки.
источник
cat big_file | tail
или дажеcat big_file | tee big_file.cpy | tail
очень часто для этого ускорить.