perché la stampa con Matplotlib è così lenta?


100

Attualmente sto valutando diverse librerie di plottaggio Python. In questo momento sto provando matplotlib e sono piuttosto deluso dalle prestazioni. Il seguente esempio è stato modificato dagli esempi di SciPy e mi dà solo ~ 8 fotogrammi al secondo!

Qualche modo per velocizzarlo o dovrei scegliere una libreria di plottaggio diversa?

from pylab import *
import time

ion()
fig = figure()
ax1 = fig.add_subplot(611)
ax2 = fig.add_subplot(612)
ax3 = fig.add_subplot(613)
ax4 = fig.add_subplot(614)
ax5 = fig.add_subplot(615)
ax6 = fig.add_subplot(616)

x = arange(0,2*pi,0.01)
y = sin(x)
line1, = ax1.plot(x, y, 'r-')
line2, = ax2.plot(x, y, 'g-')
line3, = ax3.plot(x, y, 'y-')
line4, = ax4.plot(x, y, 'm-')
line5, = ax5.plot(x, y, 'k-')
line6, = ax6.plot(x, y, 'p-')

# turn off interactive plotting - speeds things up by 1 Frame / second
plt.ioff()


tstart = time.time()               # for profiling
for i in arange(1, 200):
    line1.set_ydata(sin(x+i/10.0))  # update the data
    line2.set_ydata(sin(2*x+i/10.0))
    line3.set_ydata(sin(3*x+i/10.0))
    line4.set_ydata(sin(4*x+i/10.0))
    line5.set_ydata(sin(5*x+i/10.0))
    line6.set_ydata(sin(6*x+i/10.0))
    draw()                         # redraw the canvas

print 'FPS:' , 200/(time.time()-tstart)

Quanto segue potrebbe essere rilevante: stackoverflow.com/questions/5003094/…
NPE

2
@aix - Glumpy ha aiutato solo in questo esempio perché aveva a che fare con la visualizzazione rapida dei dati delle immagini. Non aiuterà in questo caso.
Joe Kington,

1
Prova a cambiare il backend. Vedi la mia risposta: stackoverflow.com/a/30655528/2066079 . o questa FAQ sui backend: matplotlib.org/faq/usage_faq.html#what-is-a-backend
dberm22

Risposte:


115

Prima di tutto, (anche se questo non cambierà affatto le prestazioni) prendi in considerazione la pulizia del tuo codice, in modo simile a questo:

import matplotlib.pyplot as plt
import numpy as np
import time

x = np.arange(0, 2*np.pi, 0.01)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)
styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
lines = [ax.plot(x, y, style)[0] for ax, style in zip(axes, styles)]

fig.show()

tstart = time.time()
for i in xrange(1, 20):
    for j, line in enumerate(lines, start=1):
        line.set_ydata(np.sin(j*x + i/10.0))
    fig.canvas.draw()

print 'FPS:' , 20/(time.time()-tstart)

Con l'esempio sopra, ottengo circa 10 fps.

Solo una breve nota, a seconda del tuo esatto caso d'uso, matplotlib potrebbe non essere un'ottima scelta. È orientato verso cifre di qualità da pubblicazione, non visualizzazione in tempo reale.

Tuttavia, ci sono molte cose che puoi fare per accelerare questo esempio.

Ci sono due ragioni principali per cui questo è così lento.

1) Il richiamo fig.canvas.draw()ridisegna tutto . È il tuo collo di bottiglia. Nel tuo caso, non è necessario ridisegnare elementi come i confini degli assi, le etichette di spunta, ecc.

2) Nel tuo caso, ci sono molte sottotrame con molte etichette di graduazione. Questi richiedono molto tempo per disegnare.

Entrambi questi possono essere risolti utilizzando blitting.

Per eseguire il blitting in modo efficiente, dovrai utilizzare il codice specifico del backend. In pratica, se sei davvero preoccupato per le animazioni fluide, di solito stai incorporando i grafici matplotlib in una sorta di toolkit della GUI, comunque, quindi questo non è un grosso problema.

Tuttavia, senza sapere qualcosa di più su quello che stai facendo, non posso aiutarti.

Tuttavia, esiste un modo per farlo neutrale rispetto alla gui che è ancora ragionevolmente veloce.

import matplotlib.pyplot as plt
import numpy as np
import time

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)

fig.show()

# We need to draw the canvas before we start animating...
fig.canvas.draw()

styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
def plot(ax, style):
    return ax.plot(x, y, style, animated=True)[0]
lines = [plot(ax, style) for ax, style in zip(axes, styles)]

# Let's capture the background of the figure
backgrounds = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axes]

tstart = time.time()
for i in xrange(1, 2000):
    items = enumerate(zip(lines, axes, backgrounds), start=1)
    for j, (line, ax, background) in items:
        fig.canvas.restore_region(background)
        line.set_ydata(np.sin(j*x + i/10.0))
        ax.draw_artist(line)
        fig.canvas.blit(ax.bbox)

print 'FPS:' , 2000/(time.time()-tstart)

Questo mi dà ~ 200 fps.

Per renderlo un po 'più conveniente, c'è un animationsmodulo nelle versioni recenti di matplotlib.

Come esempio:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)

fig, axes = plt.subplots(nrows=6)

styles = ['r-', 'g-', 'y-', 'm-', 'k-', 'c-']
def plot(ax, style):
    return ax.plot(x, y, style, animated=True)[0]
lines = [plot(ax, style) for ax, style in zip(axes, styles)]

def animate(i):
    for j, line in enumerate(lines, start=1):
        line.set_ydata(np.sin(j*x + i/10.0))
    return lines

# We'd normally specify a reasonable "interval" here...
ani = animation.FuncAnimation(fig, animate, xrange(1, 200), 
                              interval=0, blit=True)
plt.show()

il tuo codice è davvero molto veloce, tuttavia finisco con 2000 linee per asse! in qualche modo "line.set_ydata" crea una nuova riga invece di aggiornarla - o semplicemente lo sfondo non viene cancellato? Inoltre, perché la tua versione è molto più veloce? solo perché hai lasciato cadere "draw ()" e lo hai sostituito con "ax.draw_artist"?
memyself

In quale esempio? (Li ho testati, ma è possibile copiare e incollare la versione sbagliata nella risposta.) Inoltre, quale versione di matplotlib stai usando?
Joe Kington

4
ecco un collegamento all'immagine risultante i.imgur.com/aBRFz.png potrebbe essere un artefatto causato dalla mia scheda grafica?
memyself

7
Stavo vedendo la stessa cosa che memyself stava vedendo in i.imgur.com/aBRFz.png fino a quando non ho spostato l'acquisizione dello sfondo sotto il fig.show ().
Michael Browne

4
Bello, ma animationsembra che aggiorni la trama in base al intervalperiodo di tempo, e se volessi aggiornarlo solo quando i nuovi dati sono pronti?
Alcott

28

Matplotlib produce un'ottima grafica di qualità da pubblicazione, ma non è ottimizzata per la velocità. Ci sono una varietà di pacchetti di plottaggio Python progettati pensando alla velocità:


1
mi piace molto pyqtgraph.org/documentation per i dati di flusso in tempo reale. ottimo lavoro luke
qrtLs

11

Per iniziare, la risposta di Joe Kington fornisce ottimi consigli usando un approccio gui-neutral, e dovresti assolutamente seguire il suo consiglio (specialmente su Blitting) e metterlo in pratica. Maggiori informazioni su questo approccio, leggi il Matplotlib Cookbook

Tuttavia, l'approccio non GUI neutrale (GUI polarizzato?) È la chiave per accelerare la stampa. In altre parole, il backend è estremamente importante per la velocità di stampa.

Metti queste due righe prima di importare qualsiasi altra cosa da matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Naturalmente, ci sono varie opzioni da usare al posto di GTKAgg, ma secondo il libro di cucina menzionato prima, questa è stata la più veloce. Vedere il collegamento sui backend per ulteriori opzioni.


Funziona solo su Windows, però, conosci un modo per farlo funzionare su Mac. Il motivo per cui è specifico per Windows è che pygtk è specifico per Windows
user308827

2
pygtk non è specifico per Windows. In effetti, è un enorme dolore farlo funzionare sotto Windows (se è possibile, ho rinunciato.)
Joseph Redfern

7

Per la prima soluzione proposta da Joe Kington (.copy_from_bbox & .draw_artist & canvas.blit), ho dovuto catturare gli sfondi dopo la riga fig.canvas.draw (), altrimenti lo sfondo non aveva effetto e ho ottenuto lo stesso risultato di hai nominato. Se lo metti dopo la fig.show (), continua a non funzionare come proposto da Michael Browne.

Quindi metti la riga di sfondo dopo canvas.draw ():

[...]
fig.show()

# We need to draw the canvas before we start animating...
fig.canvas.draw()

# Let's capture the background of the figure
backgrounds = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axes]

4
dovresti semplicemente modificare la sua risposta invece di pubblicarla come una separata
endolith

1

Questo potrebbe non essere applicabile a molti di voi, ma di solito utilizzo i miei computer sotto Linux, quindi per impostazione predefinita salvo i miei grafici matplotlib come PNG e SVG. Funziona bene sotto Linux ma è insopportabilmente lento sulle mie installazioni di Windows 7 [MiKTeX sotto Python (x, y) o Anaconda], quindi ho iniziato ad aggiungere questo codice e le cose funzionano di nuovo bene:

import platform     # Don't save as SVG if running under Windows.
#
# Plot code goes here.
#
fig.savefig('figure_name.png', dpi = 200)
if platform.system() != 'Windows':
    # In my installations of Windows 7, it takes an inordinate amount of time to save
    # graphs as .svg files, so on that platform I've disabled the call that does so.
    # The first run of a script is still a little slow while everything is loaded in,
    # but execution times of subsequent runs are improved immensely.
    fig.savefig('figure_name.svg')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.