Danke für alle Kommentare! Ich habe es mit Ihrer Hilfe selbst beantwortet. Es fühlt sich allerdings schmutzig an, deine eigene Frage zu beantworten.
Frage 1:Warum ist das Drucken auf stdout langsam?
Antwort: Drucken nach stdout ist nicht von Natur aus langsam. Es ist das Terminal, mit dem Sie arbeiten, das langsam ist. Und es hat so ziemlich nichts mit I/O-Pufferung auf der Anwendungsseite zu tun (zB:Python-Dateipufferung). Siehe unten.
Frage 2:Kann es beschleunigt werden?
Antwort: Ja, das kann es, aber anscheinend nicht von der Programmseite (die Seite, die das 'Drucken' nach stdout macht). Um es zu beschleunigen, verwenden Sie einen schnelleren anderen Terminal-Emulator.
Erklärung...
Ich habe ein selbst beschriebenes "leichtes" Terminalprogramm namens wterm
ausprobiert und wurde erheblich bessere Ergebnisse. Unten ist die Ausgabe meines Testskripts (am Ende der Frage), wenn es in wterm
ausgeführt wird bei 1920 x 1200 auf demselben System, auf dem die grundlegende Druckoption mit gnome-terminal 12 Sekunden dauerte:
----- 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 ist VIEL besser als 12 s! Ich weiß nicht, ob wterm
intelligenter ist, wie es auf dem Bildschirm gerendert wird, wie ich es vorgeschlagen habe (rendern Sie den "sichtbaren" Schwanz mit einer angemessenen Bildrate), oder ob es nur "weniger macht" als gnome-terminal
. Für die Zwecke meiner Frage habe ich jedoch die Antwort. gnome-terminal
ist langsam.
Also - Wenn Sie ein lange laufendes Skript haben, das Ihrer Meinung nach langsam ist und riesige Mengen an Text nach stdout ausspuckt ... versuchen Sie es mit einem anderen Terminal und sehen Sie, ob es besser ist!
Beachten Sie, dass ich ziemlich zufällig wterm
gezogen habe aus den Ubuntu/Debian-Repositories. Dieser Link könnte dasselbe Terminal sein, aber ich bin mir nicht sicher. Andere Terminalemulatoren habe ich nicht getestet.
Update:Weil es mich kratzen musste, habe ich einen ganzen Haufen anderer Terminal-Emulatoren mit dem gleichen Skript und Vollbild (1920x1200) getestet. Meine manuell gesammelten Statistiken sind hier:
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
Die aufgezeichneten Zeiten werden manuell erfasst, aber sie waren ziemlich konsistent. Ich habe den besten (ish) Wert aufgezeichnet. YMMV, offensichtlich.
Als Bonus war es eine interessante Tour durch einige der verschiedenen verfügbaren Terminal-Emulatoren! Ich bin erstaunt, dass sich mein erster „alternativer“ Test als der beste von allen erwiesen hat.
Ihre Umleitung bewirkt wahrscheinlich nichts, da Programme feststellen können, ob ihre Ausgabe-FD auf ein tty zeigt.
Es ist wahrscheinlich, dass stdout zeilengepuffert ist, wenn es auf ein Terminal zeigt (dasselbe wie stdout
von C Stream-Verhalten).
Versuchen Sie als amüsantes Experiment, die Ausgabe an cat
weiterzuleiten .
Ich habe mein eigenes amüsantes Experiment versucht, und hier sind die Ergebnisse.
$ 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
Wie kann es sein, dass das Schreiben auf eine physische Festplatte VIEL schneller ist als das Schreiben auf den "Bildschirm" (vermutlich eine Operation mit nur RAM) und effektiv so schnell ist wie das einfache Ablegen in den Müll mit /dev/null?
Herzlichen Glückwunsch, Sie haben gerade die Bedeutung der E/A-Pufferung entdeckt. :-)
Die Diskette erscheint um schneller zu sein, weil es stark gepuffert ist:alle Pythons write()
Aufrufe werden zurückgegeben, bevor etwas tatsächlich auf die physische Festplatte geschrieben wird. (Das Betriebssystem erledigt dies später und kombiniert viele tausend einzelne Schreibvorgänge zu großen, effizienten Blöcken.)
Das Terminal hingegen puffert wenig oder gar nicht:jedes einzelne print
/ write(line)
wartet auf die volle schreiben (d. h. auf dem Ausgabegerät anzeigen), um abzuschließen.
Um den Vergleich fair zu gestalten, müssen Sie dafür sorgen, dass der Dateitest dieselbe Ausgabepufferung wie das Terminal verwendet, was Sie tun können, indem Sie Ihr Beispiel ändern zu:
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
Ich habe Ihren Dateischreibtest auf meinem Rechner ausgeführt, und mit Pufferung sind es auch hier 0,05 s für 100.000 Zeilen.
Mit den obigen Modifikationen zum ungepufferten Schreiben dauert es jedoch 40 Sekunden, um nur 1.000 Zeilen auf die Platte zu schreiben. Ich habe es aufgegeben, auf 100.000 Zeilen zu warten, um sie zu schreiben, aber wenn ich aus dem Vorhergehenden extrapoliere, würde es über eine Stunde dauern .
Das relativiert die 11 Sekunden des Terminals, nicht wahr?
Um Ihre ursprüngliche Frage zu beantworten, ist das Schreiben an ein Terminal eigentlich blitzschnell, alles in allem, und es gibt nicht viel Platz, um es viel schneller zu machen (aber die einzelnen Terminals unterscheiden sich in ihrer Arbeit; siehe Russ 'Kommentar dazu). Antwort).
(Sie könnten mehr Schreibpuffer hinzufügen, wie bei Festplatten-E/A, aber dann würden Sie nicht sehen, was in Ihr Terminal geschrieben wurde, bis der Puffer geleert ist. Es ist ein Kompromiss:Interaktivität versus Masseneffizienz.)
Über die technischen Details kann ich nichts sagen, weil ich sie nicht kenne, aber das wundert mich nicht:Das Terminal ist nicht darauf ausgelegt, so viele Daten zu drucken. In der Tat stellen Sie sogar einen Link zu einer Menge GUI-Zeug bereit, das es jedes Mal tun muss, wenn Sie etwas drucken möchten! Beachten Sie das, wenn Sie das Skript mit pythonw
aufrufen stattdessen dauert es keine 15 Sekunden; Dies ist ausschließlich ein GUI-Problem. stdout
umleiten in eine Datei, um dies zu vermeiden:
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):
...