¿Por qué trazar con Matplotlib es tan lento?

100

Actualmente estoy evaluando diferentes bibliotecas de trazado de Python. Ahora mismo estoy probando matplotlib y estoy bastante decepcionado con el rendimiento. El siguiente ejemplo está modificado a partir de ejemplos de SciPy y me da solo ~ 8 cuadros por segundo.

¿Alguna forma de acelerar esto o debería elegir una biblioteca de trazado diferente?

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)
memyself
fuente
Lo siguiente podría ser relevante: stackoverflow.com/questions/5003094/…
NPE
2
@aix: Glumpy solo ayudó en ese ejemplo porque estaba lidiando con la visualización rápida de datos de imágenes. No ayudará en este caso.
Joe Kington
1
Intente cambiar el backend. Vea mi respuesta: stackoverflow.com/a/30655528/2066079 . o las preguntas frecuentes sobre backends: matplotlib.org/faq/usage_faq.html#what-is-a-backend
dberm22

Respuestas:

115

En primer lugar, (aunque esto no cambiará el rendimiento en absoluto) considere limpiar su código, similar a esto:

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 el ejemplo anterior, obtengo alrededor de 10 fps.

Solo una nota rápida, dependiendo de su caso de uso exacto, matplotlib puede no ser una gran opción. Está orientado a cifras de calidad de publicación, no a visualización en tiempo real.

Sin embargo, hay muchas cosas que puede hacer para acelerar este ejemplo.

Hay dos razones principales por las que esto es tan lento como lo es.

1) Llamar fig.canvas.draw()redibuja todo . Es tu cuello de botella. En su caso, no necesita volver a dibujar cosas como los límites de los ejes, las etiquetas de marca, etc.

2) En su caso, hay muchas subtramas con muchas etiquetas de verificación. Estos toman mucho tiempo para dibujar.

Ambos pueden solucionarse mediante blitting.

Para hacer blitting de manera eficiente, deberá usar código específico de backend. En la práctica, si está realmente preocupado por las animaciones fluidas, generalmente está incrustando gráficos de matplotlib en algún tipo de kit de herramientas de interfaz gráfica de usuario, por lo que esto no es un gran problema.

Sin embargo, sin saber un poco más sobre lo que estás haciendo, no puedo ayudarte en eso.

No obstante, existe una forma de hacerlo sin interfaz gráfica de usuario que sigue siendo razonablemente rápida.

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)

Esto me da ~ 200 fps.

Para que esto sea un poco más conveniente, hay una animations módulo en las versiones recientes de matplotlib.

Como ejemplo:

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()
Joe Kington
fuente
su código es muy rápido, sin embargo, ¡termino con 2000 líneas por eje! de alguna manera "line.set_ydata" crea una nueva línea en lugar de actualizarla - ¿o simplemente no se borra el fondo? Además, ¿por qué tu versión es mucho más rápida? sólo porque eliminó "draw ()" y lo reemplazó con "ax.draw_artist"?
memyself
¿En qué ejemplo? (Los probé, pero es posible copiar y pegar la versión incorrecta en la respuesta). Además, ¿qué versión de matplotlib estás usando?
Joe Kington
4
aquí hay un enlace a la imagen resultante i.imgur.com/aBRFz.png ¿ Podría ser esto un artefacto causado por mi tarjeta gráfica?
memyself
7
Estaba viendo lo mismo que Memyself estaba viendo en i.imgur.com/aBRFz.png hasta que moví la captura de fondo debajo de fig.show ().
Michael Browne
4
Bien, pero animationparece actualizar el gráfico por intervalperíodo de tiempo, ¿qué pasa si solo quiero actualizarlo cuando haya nuevos datos listos?
Alcott
28

Matplotlib produce excelentes gráficos con calidad de publicación, pero no está muy bien optimizado para la velocidad. Hay una variedad de paquetes de trazado de Python que están diseñados teniendo en cuenta la velocidad:

Luke
fuente
1
Disfruto mucho de pyqtgraph.org/documentation para obtener datos de transmisión en tiempo real. gran trabajo luke
qrtLs
11

Para empezar, la respuesta de Joe Kington proporciona muy buenos consejos utilizando un enfoque de interfaz gráfica de usuario neutral, y definitivamente debe seguir su consejo (especialmente sobre Blitting) y ponerlo en práctica. Más información sobre este enfoque, lea el libro de cocina de Matplotlib

Sin embargo, el enfoque no neutral en GUI (¿sesgado en GUI?) Es clave para acelerar el trazado. En otras palabras, el backend es extremadamente importante para trazar la velocidad.

Coloque estas dos líneas antes de importar cualquier otra cosa de matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Por supuesto, hay varias opciones para usar en lugar de GTKAgg, pero según el libro de cocina mencionado anteriormente, esta fue la más rápida. Consulte el enlace sobre backends para obtener más opciones.

dberm22
fuente
Sin embargo, esto solo funciona en Windows, ¿conoce alguna forma de hacerlo funcionar en Mac? La razón es que las ventanas específico es que pygtk es específico de Windows
user308827
2
pygtk no es específico de Windows. De hecho, es un gran dolor hacer que funcione bajo Windows (si es posible, me he rendido)
Joseph Redfern
7

Para la primera solución propuesta por Joe Kington (.copy_from_bbox & .draw_artist & canvas.blit), tuve que capturar los fondos después la línea fig.canvas.draw (), de lo contrario, el fondo no tuvo ningún efecto y obtuve el mismo resultado que Mencionaste. Si lo coloca después de fig.show () todavía no funciona como lo propone Michael Browne.

Así que simplemente ponga la línea de fondo después de 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]
Sebastián
fuente
4
solo debe editar su respuesta en lugar de publicarla como una separada
endolito
1

Es posible que esto no se aplique a muchos de ustedes, pero generalmente estoy operando mis computadoras bajo Linux, por lo que de manera predeterminada guardo mis gráficos de matplotlib como PNG y SVG. Esto funciona bien en Linux, pero es insoportablemente lento en mis instalaciones de Windows 7 [MiKTeX en Python (x, y) o Anaconda], así que decidí agregar este código y las cosas funcionan bien allí nuevamente:

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')
marisano
fuente