Pourquoi l'impression sur stdout est-elle si lente? Peut-il être accéléré?
j'ai toujours été étonné/frustré par le temps qu'il faut pour simplement sortir sur le terminal avec une déclaration d'impression. Après quelques journalisation douloureusement lente récente j'ai décidé de regarder dans elle et ai été assez surpris de constater que presque tous le temps passé attend le terminal pour traiter les résultats.
peut-on accélérer l'écriture à stdout?
j'ai écrit un script (' print_timer.py
" au bas de cette question) pour comparer le timing lors de l'écriture des lignes 100k à stdout, au fichier, et avec stdout redirigé à /dev/null
. Voici le résultat de timing:
$ 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
Wow. Pour s'assurer que python ne fait pas quelque chose dans les coulisses comme reconnaître que j'ai réassigné stdout à /dev/null ou quelque chose, j'ai fait la redirection en dehors du 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
donc ce n'est pas un truc de python, c'est juste le terminal. J'ai toujours su décharger la production vers /dev/null accéléré les choses, mais jamais pensé que c'était que d'importants!
ça m'étonne de la lenteur du tty. Comment se fait-il que l'écriture sur un disque physique soit beaucoup plus rapide que l'écriture sur "l'écran" (probablement une opération tout-RAM), et qu'elle soit aussi rapide que de simplement jeter à la poubelle avec /dev/null?
ce lien explique comment le terminal va bloquer l'entrée/sortie pour qu'il puisse " analyser [l'entrée], mettre à jour son cadre tampon, communiquer avec le serveur X pour faire défiler la fenêtre et ainsi de suite" ... mais je ne suis pas entièrement l'obtenir. Qu'est-ce qui peut prendre autant de temps?
Je m'attends à ce qu'il n'y ait pas d'issue (à moins d'une mise en œuvre plus rapide d'un ATS? mais je me suis dit que je demanderais quand même.
mise à jour: après avoir lu certains commentaires, je me suis demandé quel impact ma taille d'écran avait réellement sur le temps d'impression, et il a une certaine signification. La très lente les nombres ci-dessus sont avec mon terminal Gnome gonflé à 1920x1200. Si je le réduis très petit je reçois...
-----
timing summary (100k lines each)
-----
print : 2.920 s
write to file (+fsync) : 0.121 s
print with stdout = /dev/null : 0.048 s
c'est certainement mieux (~4x), mais cela ne change pas ma question. Il n'y a que qui ajoute à ma question car je ne comprends pas pourquoi le rendu de l'écran terminal devrait ralentir une application en écrivant à stdout. Pourquoi mon programme doit-il attendre que le rendu d'écran continue?
est-ce que toutes les applications de terminal/tty ne sont pas créés égaux? Je n'ai pas encore l'expérience. Il me semble vraiment qu'un terminal devrait être capable de tamponner toutes les données entrantes, de les analyser/les rendre invisibles, et de rendre seulement le morceau le plus récent qui est visible dans la configuration actuelle de l'écran à un taux de trame raisonnable. Donc, si je peux écrire + fsync sur le disque en ~0.1 secondes, un terminal devrait être capable d'effectuer la même opération dans quelque chose de cet ordre (avec peut-être quelques mises à jour d'écran pendant qu'il l'a fait).
je suis encore un peu en espérant qu'il y a un réglage d'ATS qui peut être modifié du côté de l'application pour rendre ce comportement meilleur pour le programmeur. Si c'est strictement un problème d'application terminal, alors ça n'a peut-être même pas sa place sur StackOverflow?
Qu'est-ce que je rate?
voici le programme python utilisé pour générer le timing:
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 sn" % (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 sn" % (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 sn" % (cmd, t)
print >> sys.stderr, "-----"
print >> sys.stderr, "timing summary (100k lines each)"
print >> sys.stderr, "-----"
print >> sys.stderr, summary
6 réponses
comment se fait-il que l'écriture sur le disque physique soit beaucoup plus rapide que l'écriture sur" l'écran " (probablement une opération tout-RAM), et soit effectivement aussi rapide que de simplement jeter à la poubelle avec /dev/null?
félicitations, vous venez de découvrir l'importance de la mise en tampon des entrées/sorties. :- )
le disque apparaît pour être plus rapide, car il est fortement tamponné: tous les appels write()
de Python sont retourner avant que quelque chose soit réellement écrit sur le disque physique. (L'OS le fait plus tard, en combinant plusieurs milliers d'individus écrit dans un grand, efficace morceaux.)
le terminal, d'autre part, fait peu ou pas de tampon: chaque individu print
/ write(line)
attend la complète écrire (c.-à-d. affichage à l'appareil de sortie) à compléter.
pour rendre la comparaison juste, vous devez faire le test de fichier utiliser le même sortie buffering comme le terminal, ce que vous pouvez faire en modifiant votre exemple en:
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
j'ai lancé votre test d'écriture de fichier sur ma machine, et avec la mise en tampon, il est aussi 0.05 S ici pour 100.000 lignes.
cependant, avec les modifications ci-dessus pour écrire libre, il faut 40 secondes pour écrire seulement 1.000 lignes sur le disque. J'ai abandonné l'attente de 100.000 lignes à écrire, mais extrapolation à partir de la précédente, il faudrait plus d'une heure .
qui met les 11 secondes du terminal en perspective, n'est-ce pas?
donc pour répondre à votre question originale, écrire à un terminal est en fait extrêmement rapide, toutes les choses considérées, et il n'y a pas beaucoup de place pour le rendre beaucoup plus rapide (mais les terminaux individuels varient dans combien de travail ils font; voir le commentaire de Russ à cette réponse).
(vous pouvez ajouter plus de tampon d'écriture, comme avec l'entrée / sortie du disque, mais ensuite vous Je ne verrais pas ce qui a été écrit sur votre terminal avant que le tampon ne soit vidé. C'est un compromis: l'interactivité contre vrac efficacité.)
Merci pour tous les commentaires! J'ai fini de répondre moi-même avec votre aide. C'est sale de répondre à ta propre question.
Question 1: Pourquoi l'impression sur stdout est-elle lente?
réponse: L'impression à stdout est et non lente par nature. C'est le terminal vous travaillez avec qui est lent. Et il a à peu près zéro à voir avec I / O tampering sur le côté application (par exemple: tampon de fichiers python). Voir ci-dessous.
Question 2: Peut-on accélérer?
Réponse: Oui, mais apparemment pas du programme (le côté faisant le '"impression" sur la sortie standard stdout). Pour l'accélérer, utilisez un émulateur de terminal différent et plus rapide.
explication...
j'ai essayé un programme de terminal auto-décrit "léger" appelé wterm
et obtenu de manière significative de meilleurs résultats. Ci - dessous est la sortie de mon script de test (en bas de la question) lors de l'exécution dans wterm
à 1920x1200 sur le même système où l'option d'impression de base a pris 12s en utilisant 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,26 s est bien mieux que 12s! Je ne sais pas si wterm
est plus intelligent sur la façon dont il rend à l'écran le long de la façon dont je suggérais (rendre le "visible" la taille de la queue à une cadence raisonnable), ou si elle "fait tout simplement moins" que gnome-terminal
. Pour les besoins de ma question, j'ai eu la réponse. gnome-terminal
est lent.
donc-si vous avez un script de longue durée que vous sentez est lent et il vomit des quantités massives de texte à stdout... essayez un autre terminal et voir si c'est mieux!
notez que j'ai tiré à peu près au hasard wterm
des dépôts ubuntu/debian. Ce lien pourrait être le même terminal, mais je ne suis pas sûr. Je n'ai testé aucun autre émulateur de terminal.
mise à Jour: Parce que j'ai eu à gratter la démangeaison, j'ai testé tout un tas d'autres émulateurs de terminal avec le même script et en plein écran (1920 x 1200). Mes statistiques collectées manuellement sont ici:
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
les temps enregistrés sont collectés manuellement, mais ils étaient assez constants. J'ai enregistré la meilleure(ish) valeur. YMMV, évidemment.
en prime, ce fut une visite intéressante de quelques-uns des différents émulateurs de terminaux disponibles là-bas! Je suis étonné que mon premier test "alternatif" se soit avéré être le meilleur du groupe.
votre redirection ne fait probablement rien car les programmes peuvent déterminer si leur sortie FD pointe vers un tty.
il est probable que stdout soit doté d'un tampon de ligne lorsqu'il pointe vers un terminal (le même que C stdout
comportement du cours d'eau).
comme expérience amusante, essayez de raccorder la sortie à cat
.
j'ai essayé ma propre expérience amusante, et voici le résultat.
$ 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
Je ne peux pas parler des détails techniques parce que je ne les connais pas, mais cela ne me surprend pas: le terminal n'a pas été conçu pour imprimer beaucoup de données comme celles-ci. En effet, vous même fournir un lien à une charge de trucs GUI qu'il doit faire à chaque fois que vous voulez imprimer quelque chose! Notez que si vous appelez le script avec pythonw
à la place, cela ne prend pas 15 secondes; c'est entièrement un problème D'interface graphique. Rediriger stdout
vers un fichier pour éviter cela:
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):
...
L'impression au terminal va être lente. Malheureusement, à défaut d'écrire une nouvelle implémentation de terminal, Je ne vois pas comment vous pourriez accélérer cela de manière significative.
en plus de la sortie qui passe probablement en mode tampon de ligne, la sortie vers un terminal fait également couler vos données dans un terminal et une ligne série avec un débit maximum, ou un pseudo-terminal et un processus séparé qui manipule une boucle d'événement d'affichage, rendant des caractères à partir d'une fonte, déplaçant des bits d'affichage pour mettre en œuvre un écran défilant. Le dernier scénario est probablement réparties sur plusieurs processus (par exemple, telnet, client/serveur, le terminal de l'application, le serveur d'affichage X11) il y a donc des problèmes de changement de contexte et de latence.