¿Por qué es tan lento trazar con Matplotlib?

Actualmente estoy evaluando diferentes bibliotecas de trazado de python. Ahora mismo estoy probando matplotlib y estoy bastante decepcionado con el rendimiento. El siguiente ejemplo se modifica de los ejemplos de SciPy y me da solo ~ 8 cuadros por segundo.

¿Alguna forma de acelerar esto o debo 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) 

En primer lugar, (aunque esto no cambiará el rendimiento en absoluto) considere limpiar su código, de manera 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 elecció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 a fig.canvas.draw() vuelve a dibujar todo . Es tu cuello de botella. En su caso, no necesita volver a dibujar cosas como los límites de los ejes, las tags de tic tac, etc.

2) En su caso, hay muchas subplots con muchas tags de tick. Estos tardan mucho tiempo en dibujar.

Ambos pueden ser arreglados usando blitting.

Para hacer blitting eficientemente, tendrás que usar un código específico de back-end. En la práctica, si estás realmente preocupado por las animaciones suaves, por lo general, estás incrustando diagtwigs de matplotlib en algún tipo de juego de herramientas de GUI, así que esto no es un gran problema.

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

No obstante, hay una forma guia-neutral de hacerlo que todavía es 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.restre_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 ~ 200fps.

Para hacer esto un poco más conveniente, hay un módulo de animations en 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() 

Matplotlib crea 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:

Para comenzar, la respuesta de Joe Kington proporciona muy buenos consejos con un enfoque neutral, y definitivamente debes 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-GUI-neutral (¿sesgado por la 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 desde 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. Vea el enlace sobre backends para más opciones.

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

Así que simplemente coloque 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] 

Es posible que esto no se aplique a muchos de ustedes, pero generalmente estoy operando mis computadoras en 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 comencé a agregar este código, y las cosas funcionan bien otra vez:

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