print and python linux printing stdout tty

and - python print console



¿Por qué la impresión en stdout es tan lenta? ¿Se puede acelerar? (6)

Siempre me ha sorprendido o frustrado el tiempo que lleva simplemente enviar a la terminal con una declaración de impresión. Después de un registro lento y dolorosamente reciente, decidí investigarlo y me sorprendió bastante descubrir que casi todo el tiempo invertido está esperando a que la terminal procese los resultados.

¿Se puede acelerar la escritura en stdout de alguna manera?

Escribí un guión ('' print_timer.py '' en la parte inferior de esta pregunta) para comparar el tiempo al escribir 100k líneas a stdout, a archivo, y con stdout redirigido a /dev/null . Aquí está el resultado del tiempo:

$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

Guau. Para asegurarme de que python no está haciendo algo detrás de escena, como reconocer que reasigné stdout a / dev / null o algo así, hice la redirección fuera del script ...

$ 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

Entonces no es un truco de pitón, es solo la terminal. Siempre supe que la salida de dumping a / dev / null aceleraba las cosas, ¡pero nunca creí que fuera tan significativo!

Me sorprende lo lento que es el tty. ¿Cómo puede ser que escribir en el disco físico sea MUCHO más rápido que escribir en la "pantalla" (probablemente una opción de todas las RAM), y efectivamente es tan rápido como simplemente descargarlo a la basura con / dev / null?

Este enlace habla de cómo el terminal bloqueará E / S para que pueda "analizar [la entrada], actualizar su búfer de cuadros, comunicarse con el servidor X para desplazarse por la ventana y así sucesivamente" ... pero yo no conseguirlo por completo ¿Qué puede tomar tanto tiempo?

Espero que no haya escapatoria (¿menos que una implementación tty más rápida?), Pero imagino que preguntaría de todos modos.

ACTUALIZACIÓN: después de leer algunos comentarios, me pregunté cuánto impacto tiene realmente el tamaño de la pantalla en el tiempo de impresión, y tiene cierta importancia. Los números realmente lentos arriba son con mi terminal Gnome volado hasta 1920x1200. Si lo reduzco muy pequeño, me sale ...

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

Eso es ciertamente mejor (~ 4x), pero no cambia mi pregunta. Solo agrega a mi pregunta, ya que no entiendo por qué la representación de la pantalla del terminal debe ralentizar la escritura de una aplicación a stdout. ¿Por qué mi programa necesita esperar a que continúe la representación de la pantalla?

¿Todas las aplicaciones de terminal / tty no son iguales? Todavía tengo que experimentar. Realmente me parece que un terminal debe ser capaz de almacenar todos los datos entrantes, analizarlos / renderizarlos de manera invisible, y solo renderizar el fragmento más reciente que es visible en la configuración de pantalla actual a una tasa de fotogramas sensata. Entonces, si puedo escribir + fsync en el disco en ~ 0.1 segundos, un terminal debería ser capaz de completar la misma operación en algo de ese orden (con algunas actualizaciones de pantalla mientras lo hacía).

Todavía estoy esperando que haya una configuración tty que se pueda cambiar desde el lado de la aplicación para mejorar el comportamiento del programador. Si esto es estrictamente un problema de aplicación de terminal, ¿entonces esto quizás ni siquiera pertenece a StackOverflow?

¿Qué me estoy perdiendo?

Aquí está el programa python usado para generar el tiempo:

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


¿Cómo puede ser que escribir en el disco físico sea MUCHO más rápido que escribir en la "pantalla" (probablemente una opción de todas las RAM), y efectivamente es tan rápido como simplemente descargarlo a la basura con / dev / null?

Felicitaciones, acaba de descubrir la importancia del almacenamiento en búfer de E / S. :-)

El disco parece ser más rápido, porque está muy protegido: todas las llamadas write() Python vuelven antes de que se escriba algo en el disco físico. (El SO lo hace más adelante, combinando muchos miles de escrituras individuales en fragmentos grandes y eficientes).

El terminal, por otro lado, tiene poco o ningún almacenamiento en búfer: cada print / write(line) individual write(line) espera a que se complete la escritura completa (es decir, pantalla a dispositivo de salida).

Para que la comparación sea justa, debe hacer que la prueba de archivo use el mismo búfer de salida que el terminal, lo cual puede hacer modificando su ejemplo a:

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

Ejecuté su prueba de escritura de archivos en mi máquina, y con el almacenamiento en búfer, también 0.05s aquí para 100,000 líneas.

Sin embargo, con las modificaciones anteriores para escribir sin búfer, lleva 40 segundos escribir solo 1,000 líneas en el disco. Dejé de esperar a que escribieran 100.000 líneas, pero extrapolando de la anterior, tomaría más de una hora .

Eso pone los 11 segundos de la terminal en perspectiva, ¿no es así?

Entonces, para responder a su pregunta original, escribir en un terminal es increíblemente rápido, considerando todo, y no hay mucho espacio para hacerlo mucho más rápido (pero los terminales individuales varían en la cantidad de trabajo que hacen; vea el comentario de Russ al respecto responder).

(Podría agregar más búfer de escritura, como con E / S de disco, pero luego no vería lo que se escribió en su terminal hasta después de que se vacíe el búfer. Es una compensación: interactividad versus eficiencia masiva).


¡Gracias por todos los comentarios! He terminado respondiéndolo yo mismo con tu ayuda. Sin embargo, se siente sucio respondiendo tu propia pregunta.

Pregunta 1: ¿Por qué la impresión en stdout es lenta?

Respuesta: Imprimir a stdout no es inherentemente lento. Es la terminal con la que trabajas que es lenta. Y tiene mucho que ver con el almacenamiento en búfer de E / S en el lado de la aplicación (por ejemplo, el búfer de archivos de Python). Vea abajo.

Pregunta 2: ¿Se puede acelerar?

Respuesta: Sí, puede, pero aparentemente no desde el lado del programa (el lado que está ''imprimiendo'' a stdout). Para acelerarlo, use un emulador de terminal diferente más rápido.

Explicación...

wterm un programa wterm terminal "ligero" llamado wterm y obtuve resultados significativamente mejores. A continuación se muestra el resultado de mi secuencia de comandos de prueba (en la parte inferior de la pregunta) cuando se ejecuta en wterm a 1920x1200 en el mismo sistema donde la opción de impresión básica tardó 12 segundos usando gnome-terminal:

----- 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 es MUCHO mejor que 12s! No sé si wterm es más inteligente acerca de cómo se hace para seleccionar lo que estaba sugiriendo (representar la cola "visible" a una velocidad de fotogramas razonable), o si simplemente "hace menos" que gnome-terminal . A los fines de mi pregunta, tengo la respuesta, sin embargo. gnome-terminal es lento.

Por lo tanto, si tiene un script de ejecución larga que cree que es lento y arroja grandes cantidades de texto a stdout ... ¡pruebe con un terminal diferente y vea si es mejor!

Tenga en cuenta que prácticamente saqué wterm al azar de los repositorios ubuntu / debian. Este enlace podría ser el mismo terminal, pero no estoy seguro. No probé ningún otro emulador de terminal.

Actualización: debido a que tuve que rascarme la comezón, probé una pila completa de otros emuladores de terminal con el mismo script y pantalla completa (1920x1200). Mis estadísticas recopiladas manualmente están aquí:

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

Los tiempos registrados se recopilan manualmente, pero fueron bastante consistentes. Grabé el mejor valor (ish). YMMV, obviamente.

Como extra, ¡fue un recorrido interesante por algunos de los varios emuladores de terminal disponibles en el mercado! Estoy sorprendido de que mi primera prueba "alternativa" resulte ser la mejor del grupo.


Además de la salida probablemente por defecto en un modo de línea-buffer, la salida a un terminal también está causando que sus datos fluyan a una línea de terminal y serie con un rendimiento máximo, o un pseudo-terminal y un proceso separado que maneja una pantalla bucle de evento, representación de caracteres de alguna fuente, desplazamiento de bits de visualización para implementar una visualización de desplazamiento. El último escenario probablemente se distribuya en múltiples procesos (por ejemplo, servidor / cliente Telnet, aplicación de terminal, servidor de pantalla X11) por lo que también hay problemas de latencia y cambio de contexto.


La impresión en el terminal va a ser lenta. Desafortunadamente, a menos que escriba una nueva implementación de terminal, realmente no veo cómo aceleraría esto significativamente.


No puedo hablar sobre los detalles técnicos porque no los conozco, pero esto no me sorprende: el terminal no fue diseñado para imprimir muchos datos como este. De hecho, incluso proporciona un enlace a una carga de cosas de GUI que tiene que hacer cada vez que desea imprimir algo. Tenga en cuenta que si llama al script con pythonw en pythonw lugar, no toma 15 segundos; esto es completamente un problema GUI. Redirija la stdout estándar a un archivo para evitar esto:

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): ...


Su redirección probablemente no haga nada ya que los programas pueden determinar si su salida FD apunta a un tty.

Es probable que stdout esté almacenado en línea cuando apunta a un terminal (lo mismo que el comportamiento del flujo de stdout de C).

Como un experimento divertido, intente conectar la salida a cat .

Probé mi propio experimento divertido, y aquí están los resultados.

$ 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