J'ai toujours été étonné/frustré par le temps qu'il faut pour simplement envoyer une sortie au terminal avec une instruction print. Après une récente journalisation douloureusement lente, j'ai décidé de me pencher sur la question et j'ai été assez surpris de constater que presque todo le temps passé à attendre que le terminal traite les résultats.
Peut-on accélérer l'écriture vers stdout d'une manière ou d'une autre ?
J'ai écrit un script (' print_timer.py
au bas de cette question) pour comparer les temps d'écriture de 100k lignes sur stdout, sur fichier, et avec stdout redirigé sur /dev/null
. Voici le résultat du chronométrage :
$ 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 être sûr que python ne fait pas quelque chose en coulisse comme reconnaître que j'ai réassigné stdout à /dev/null ou autre, 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
Ce n'est donc pas un truc de python, c'est juste le terminal. J'ai toujours su que le vidage de la sortie vers /dev/null accélérait les choses, mais je n'avais jamais pensé que c'était si important !
Je suis étonné de voir à quel point le tty est lent. Comment se fait-il que l'écriture sur le disque physique soit BEAUCOUP plus rapide que l'écriture sur l'"écran" (vraisemblablement une opération tout-RAM), et qu'elle soit effectivement aussi rapide que le simple vidage dans la poubelle avec /dev/null ?
Ce lien parle de la façon dont le terminal va bloquer les entrées/sorties pour qu'il puisse "analyser [l'entrée], mettre à jour son tampon d'image, communiquer avec le serveur X afin de faire défiler la fenêtre, etc.". ... mais je ne comprends pas tout. Qu'est-ce qui peut prendre autant de temps ?
Je suppose qu'il n'y a pas de solution (à part une implémentation plus rapide du tty ?) 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 la taille de mon écran avait sur le temps d'impression, et cela a effectivement une certaine importance. Les chiffres très lents ci-dessus sont obtenus avec mon terminal Gnome agrandi à 1920x1200. Si je le réduis très petit, j'obtiens...
-----
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. Cela ne fait que ajoute à ma question car je ne comprends pas pourquoi le rendu de l'écran du terminal devrait ralentir une application écrivant sur stdout. Pourquoi mon programme doit-il attendre le rendu d'écran pour continuer ?
Toutes les applications de type terminal/tty ne sont-elles pas égales ? Je n'ai pas encore expérimenté. J'ai vraiment l'impression qu'un terminal devrait être capable de mettre en mémoire tampon toutes les données entrantes, de les analyser/rendre invisibles et de ne rendre que le morceau le plus récent visible dans la configuration actuelle de l'écran à une fréquence d'images raisonnable. Donc, si je peux écrire+fsync sur le disque en ~0,1 seconde, un terminal devrait être capable de réaliser la même opération en quelque chose de cet ordre (avec peut-être quelques mises à jour de l'écran pendant qu'il le fait).
J'espère toujours qu'il y a un paramètre de tty qui peut être modifié du côté de l'application pour améliorer ce comportement pour le programmeur. S'il s'agit strictement d'un problème d'application de terminal, alors cette question 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 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
12 votes
Le but de l'écriture vers stdout est qu'un humain puisse lire la sortie. Aucun être humain au monde ne peut lire 10 000 lignes de texte en 12 secondes, alors quel est l'intérêt de rendre stdout plus rapide ???
19 votes
@Seun Osewa : Un exemple (qui a motivé ma question) est quand on fait des choses comme Débogage de la déclaration d'impression . Vous voulez exécuter votre programme et voir les résultats au fur et à mesure qu'ils se produisent. Vous avez évidemment raison de dire que la plupart des lignes passeront sans que vous puissiez les voir, mais lorsqu'une exception se produit (ou que vous touchez l'instruction conditionnelle getch/raw_input/sleep que vous avez soigneusement placée), vous voulez voir directement la sortie imprimée plutôt que de devoir constamment ouvrir ou rafraîchir un fichier.
3 votes
Le débogage de l'instruction d'impression est l'une des raisons pour lesquelles les périphériques tty (c'est-à-dire les terminaux) utilisent par défaut la mise en mémoire tampon de ligne au lieu de la mise en mémoire tampon de bloc : la sortie de débogage n'est pas très utile si le programme se bloque et que les dernières lignes de la sortie de débogage sont toujours dans un tampon au lieu d'être envoyées au terminal.
0 votes
@Stephen : C'est la raison pour laquelle je n'ai pas pris la peine de poursuivre les améliorations considérables qu'un commentateur prétendait obtenir en augmentant la taille du tampon. Cela va totalement à l'encontre de l'objectif de l'impression de débogage ! J'ai fait quelques expériences pendant mes recherches, mais je n'ai pas vu d'amélioration nette. Je suis toujours curieux de l'écart, mais pas vraiment.
0 votes
Parfois, pour des programmes très longs, je me contente d'imprimer la ligne courante stdout toutes les n secondes -- un peu comme un délai de rafraîchissement dans une application curses. Ce n'est pas parfait, mais cela donne une idée de ce que je suis en train de faire de temps en temps.
3 votes
@SeunOsewa Non. Non, et encore non ! Le but de l'écriture dans stdout est que c'est l'endroit standard pour écrire la sortie. Ne confondez PAS stdout avec un terminal. Cette question est entièrement fausse. L'écriture sur stdout est PAS n'est pas intrinsèquement plus lente que l'écriture dans n'importe quel autre fichier. L'écriture dans un terminal est lente. STDOUT N'EST PAS UN TERMINAL. Pardonnez cette diatribe, mais laissez-moi le répéter. Ne confondez pas stdout avec le tty. Ce sont deux choses très différentes qui se trouvent juste être souvent associées.