python pyplot Perché la stampa su stdout è così lenta? Può essere accelerato?




title matplotlib python (5)

Sono sempre stato sorpreso / frustrato da quanto tempo ci vuole per trasmettere semplicemente al terminale con una dichiarazione di stampa. Dopo alcuni recenti ritardi dolorosi, ho deciso di esaminarlo e sono rimasto piuttosto sorpreso nel constatare che quasi tutto il tempo trascorso è in attesa che il terminale elabori i risultati.

La scrittura su stdout può essere accelerata in qualche modo?

Ho scritto uno script (' print_timer.py ' in fondo a questa domanda) per confrontare i tempi durante la scrittura di linee da 100k su stdout, su file e con stdout reindirizzato su /dev/null . Ecco il risultato del tempo:

$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. Per assicurarsi che Python non stia facendo qualcosa dietro le quinte come riconoscere che ho riassegnato lo stdout a / dev / null o qualcosa del genere, ho fatto il reindirizzamento al di fuori dello 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

Quindi non è un trucco da pitone, è solo il terminale. Ho sempre saputo che l'output di dumping su / dev / null ha accelerato le cose, ma non ho mai immaginato che fosse così significativo!

Mi stupisce quanto sia lento il tty. Come può essere che scrivere su un disco fisico è VELOCE più veloce della scrittura sullo "schermo" (presumibilmente un ope tutto RAM), ed è efficace quanto basta semplicemente scaricando la spazzatura con / dev / null?

Questo link parla di come il terminale bloccherà l'I / O in modo che possa "analizzare [l'input], aggiornare il suo frame buffer, comunicare con il server X per far scorrere la finestra e così via" ... ma non lo faccio prendilo completamente. Cosa può richiedere così tanto tempo?

Mi aspetto che non ci sia via d'uscita (a meno di un'implementazione tty più veloce?) Ma la figura lo chiederei comunque.

AGGIORNAMENTO: dopo aver letto alcuni commenti mi sono chiesto quanto impatto abbia effettivamente la mia dimensione dello schermo sul tempo di stampa, e ha un certo significato. I numeri veramente lenti sopra sono con il mio terminale Gnome saltato a 1920x1200. Se lo riduco molto piccolo, ottengo ...

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

Questo è sicuramente meglio (~ 4x), ma non cambia la mia domanda. Aggiunge solo alla mia domanda perché non capisco perché il rendering dello schermo del terminale dovrebbe rallentare un'applicazione che scrive su stdout. Perché il mio programma deve attendere che il rendering dello schermo continui?

Tutte le app terminal / tty non sono state create uguali? Devo ancora sperimentare. Mi sembra davvero che un terminale dovrebbe essere in grado di bufferizzare tutti i dati in arrivo, analizzarli / renderli invisibili e solo rendere il frammento più recente che è visibile nella configurazione dello schermo corrente con una frequenza di fotogrammi ragionevole. Quindi se riesco a scrivere + fsync su disco in ~ 0,1 secondi, un terminale dovrebbe essere in grado di completare la stessa operazione in un ordine simile (con forse qualche aggiornamento dello schermo mentre lo faceva).

Sono ancora un po 'speranzoso che ci sia un'impostazione tty che può essere modificata dal lato dell'applicazione per rendere questo comportamento migliore per il programmatore. Se questo è strettamente un problema di applicazione terminale, allora questo forse non appartiene nemmeno a StackOverflow?

Cosa mi manca?

Ecco il programma python usato per generare il 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

Oltre all'output probabilmente predefinito per una modalità bufferizzata di linea, l'output su un terminale sta anche causando il flusso dei dati in un terminale e una linea seriale con un throughput massimo, o uno pseudo-terminale e un processo separato che gestisce un display ciclo di eventi, rendering di caratteri da un font, spostamento di bit di visualizzazione per implementare un display a scorrimento. Quest'ultimo scenario è probabilmente distribuito su più processi (ad es. Server / client telnet, app terminale, server di visualizzazione X11), quindi anche i problemi di cambio di contesto e latenza.


Grazie per tutti i commenti! Ho finito per rispondere io stesso con il tuo aiuto. Sembra sporco rispondere alla tua stessa domanda, però.

Domanda 1: Perché la stampa su stdout è lenta?

Risposta: la stampa su stdout non è intrinsecamente lenta. È il terminale con cui lavori è lento. E ha praticamente nulla a che fare con il buffering I / O sul lato dell'applicazione (es .: buffering dei file python). Vedi sotto.

Domanda 2: può essere accelerata?

Risposta: Sì, può, ma apparentemente non dal lato del programma (il lato che esegue la "stampa" sullo stdout). Per accelerare, utilizzare un emulatore di terminale più veloce.

Spiegazione...

Ho provato un programma terminale "leggero" auto-descritto chiamato wterm e ho ottenuto risultati significativamente migliori. Di seguito è riportato l'output del mio script di test (in fondo alla domanda) quando è in esecuzione in wterm a 1920x1200 nello stesso sistema in cui l'opzione di stampa di base ha richiesto 12 secondi con 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 è MOLTO meglio di 12s! Non so se wterm è più intelligente di come viene visualizzato sullo schermo seguendo le linee di come suggerivo (rende la coda "visibile" ad un frame rate ragionevole), o se "fa meno" di gnome-terminal . Ai fini della mia domanda ho la risposta, però. gnome-terminal è lento.

Quindi - Se hai una sceneggiatura lunga che ritieni sia lenta e sputa massicce quantità di testo sullo stdout ... prova un terminale diverso e vedi se è meglio!

Nota che ho praticamente estratto in modo casuale da repository ubuntu / debian. Questo collegamento potrebbe essere lo stesso terminale, ma non ne sono sicuro. Non ho testato nessun altro emulatore di terminale.

Aggiornamento: Poiché ho dovuto grattare il prurito, ho testato una pila di altri emulatori di terminale con lo stesso script e lo schermo intero (1920x1200). Le mie statistiche raccolte manualmente sono qui:

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

I tempi registrati vengono raccolti manualmente, ma sono piuttosto coerenti. Ho registrato il miglior valore (ish). YMMV, ovviamente.

Come bonus, è stato un tour interessante di alcuni dei vari emulatori di terminale disponibili là fuori! Sono stupito che il mio primo test "alternativo" si sia rivelato il migliore del gruppo.


La stampa sul terminale sarà lenta. Sfortunatamente, a meno di scrivere una nuova implementazione del terminale, non riesco davvero a capire come accelereresti significativamente.


Non posso parlare dei dettagli tecnici perché non li conosco, ma questo non mi sorprende: il terminale non è stato progettato per la stampa di molti dati come questo. In effetti, fornisci anche un collegamento a un carico di elementi della GUI che deve fare ogni volta che vuoi stampare qualcosa! Si noti che se si chiama lo script con pythonw , invece, non ci vogliono 15 secondi; questo è interamente un problema con la GUI. Reindirizzare lo stdout in un file per evitare questo:

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

Come può essere che scrivere su un disco fisico è VELOCE più veloce della scrittura sullo "schermo" (presumibilmente un ope tutto RAM), ed è efficace quanto basta semplicemente scaricando la spazzatura con / dev / null?

Congratulazioni, hai appena scoperto l'importanza del buffering I / O. :-)

Il disco sembra essere più veloce, perché è altamente bufferizzato: tutte le chiamate a write() di Python stanno ritornando prima che qualsiasi cosa sia effettivamente scritta sul disco fisico. (Il sistema operativo lo fa in un secondo momento, combinando molte migliaia di scritture individuali in blocchi grandi ed efficienti).

Il terminale, d'altra parte, esegue un buffering piccolo o nullo: ogni singola print / write(line) attende la completa scrittura (cioè display per dispositivo di output) da completare.

Per rendere equo il confronto, devi fare in modo che il test del file utilizzi lo stesso buffer di output del terminale, che puoi eseguire modificando il tuo esempio in:

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

Ho eseguito il test di scrittura dei file sulla mia macchina e, con il buffering, anche qui 0.05s per 100.000 righe.

Tuttavia, con le modifiche sopra riportate per scrivere senza buffer, occorrono 40 secondi per scrivere solo 1.000 righe su disco. Ho rinunciato ad aspettare 100.000 righe da scrivere, ma estrapolando dal precedente, ci sarebbe voluto più di un'ora .

Ciò mette in prospettiva gli 11 secondi del terminale, vero?

Quindi, per rispondere alla tua domanda iniziale, scrivere su un terminale è incredibilmente veloce, tutto sommato, e non c'è molto spazio per renderlo molto più veloce (ma i singoli terminali variano in quanto lavoro fanno, vedi il commento di Russ a questo risposta).

(È possibile aggiungere più buffer di scrittura, come con I / O su disco, ma non si vedrà ciò che è stato scritto sul terminale fino a quando il buffer non viene scaricato. È un compromesso: interattività rispetto all'efficienza di massa.)





tty