pourquoi le traçage avec Matplotlib est-il si lent?

100

J'évalue actuellement différentes bibliothèques de traçage python. En ce moment, j'essaye matplotlib et je suis assez déçu de la performance. L'exemple suivant est modifié à partir d' exemples SciPy et ne me donne que ~ 8 images par seconde!

Existe-t-il des moyens d'accélérer cela ou devrais-je choisir une bibliothèque de traçage différente?

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)
moi-même
la source
Ce qui suit peut être pertinent: stackoverflow.com/questions/5003094/…
NPE
2
@aix - Glumpy n'a aidé que dans cet exemple parce qu'il traitait l'affichage rapide des données d'image. Cela n'aidera pas dans ce cas.
Joe Kington
1
Essayez de changer le backend. Voir ma réponse: stackoverflow.com/a/30655528/2066079 . ou cette FAQ sur les backends
dberm22

Réponses:

115

Tout d'abord, (bien que cela ne changera pas du tout les performances) envisagez de nettoyer votre code, comme ceci:

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)

Avec l'exemple ci-dessus, j'obtiens environ 10fps.

Juste un petit mot, en fonction de votre cas d'utilisation exact, matplotlib peut ne pas être un excellent choix. Il est orienté vers des chiffres de qualité de publication, pas vers l'affichage en temps réel.

Cependant, vous pouvez faire beaucoup de choses pour accélérer cet exemple.

Il y a deux raisons principales pour lesquelles cela est aussi lent que cela.

1) L'appel fig.canvas.draw()redessine tout . C'est votre goulot d'étranglement. Dans votre cas, vous n'avez pas besoin de redessiner des éléments tels que les limites des axes, les étiquettes de graduation, etc.

2) Dans votre cas, il y a beaucoup de sous-parcelles avec beaucoup d'étiquettes de graduation. Ceux-ci prennent beaucoup de temps à dessiner.

Ces deux problèmes peuvent être résolus en utilisant le blitting.

Pour faire du blitting efficacement, vous devrez utiliser du code spécifique au backend. En pratique, si vous êtes vraiment inquiet pour des animations fluides, vous intégrez généralement des tracés matplotlib dans une sorte de boîte à outils d'interface graphique, de toute façon, ce n'est donc pas vraiment un problème.

Cependant, sans en savoir un peu plus sur ce que vous faites, je ne peux pas vous y aider.

Néanmoins, il existe une manière de le faire, indépendante de l'interface graphique, qui est encore raisonnablement rapide.

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)

Cela me donne environ 200 images par seconde.

Pour rendre cela un peu plus pratique, il existe un animationsmodule dans les versions récentes de matplotlib.

Par exemple:

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
la source
votre code est en effet très rapide, cependant je me retrouve avec 2000 lignes par axe! "line.set_ydata" crée en quelque sorte une nouvelle ligne au lieu de la mettre à jour - ou est-ce que l'arrière-plan n'est tout simplement pas effacé? De plus, pourquoi votre version est-elle tellement plus rapide? juste parce que vous avez laissé tomber "draw ()" et l'avez remplacé par "ax.draw_artist"?
memyself
Dans quel exemple? (Je les ai testés, mais il est possible de copier-coller la mauvaise version dans la réponse.) De plus, quelle version de matplotlib utilisez-vous?
Joe Kington
4
voici un lien vers l'image résultante i.imgur.com/aBRFz.png est -ce que cela peut être un artefact causé par ma carte graphique?
memyself
7
Je voyais la même chose que memyself voyait dans i.imgur.com/aBRFz.png jusqu'à ce que je déplace la capture d'arrière-plan sous le fig.show ().
Michael Browne
4
Bien, mais animationsemble mettre à jour l'intrigue par intervalpériode de temps, que faire si je veux simplement le mettre à jour lorsque de nouvelles données sont prêtes?
Alcott
28

Matplotlib fait de superbes graphiques de qualité de publication, mais n'est pas très bien optimisé pour la vitesse. Il existe une variété de packages de traçage python conçus pour la vitesse:

Luke
la source
1
j'aime bien pyqtgraph.org/documentation pour les données de flux en temps réel. great job
luke
11

Pour commencer, la réponse de Joe Kington fournit de très bons conseils en utilisant une approche indépendante de l'interface graphique, et vous devez absolument suivre ses conseils (en particulier sur Blitting) et les mettre en pratique. Plus d'informations sur cette approche, lisez le livre de recettes Matplotlib

Cependant, l'approche non neutre sur l'interface graphique (biaisée sur l'interface graphique?) Est essentielle pour accélérer le traçage. En d'autres termes, le backend est extrêmement important pour tracer la vitesse.

Mettez ces deux lignes avant d'importer quoi que ce soit d'autre depuis matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Bien sûr, il existe différentes options à utiliser à la place GTKAgg, mais selon le livre de cuisine mentionné précédemment, c'était la plus rapide. Voir le lien sur les backends pour plus d'options.

dberm22
la source
Cela ne fonctionne que sur Windows, connaissez-vous un moyen de le faire fonctionner sur Mac. La raison pour laquelle il est spécifique à Windows est que pygtk est spécifique à Windows
user308827
2
pygtk n'est pas spécifique à Windows. En fait, c'est une énorme douleur de le faire fonctionner sous Windows (si c'est même possible, j'ai abandonné.)
Joseph Redfern
7

Pour la première solution proposée par Joe Kington (.copy_from_bbox & .draw_artist & canvas.blit), j'ai dû capturer les arrière-plans après la ligne fig.canvas.draw (), sinon l'arrière-plan n'avait aucun effet et j'ai obtenu le même résultat que vous avez mentionné. Si vous le mettez après la fig.show (), il ne fonctionne toujours pas comme proposé par Michael Browne.

Il suffit donc de mettre la ligne d'arrière-plan après le 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]
Sébastien
la source
4
vous devriez simplement modifier sa réponse au lieu de la publier séparément
endolith
1

Cela peut ne pas s'appliquer à beaucoup d'entre vous, mais j'utilise généralement mes ordinateurs sous Linux, donc par défaut, j'enregistre mes tracés matplotlib au format PNG et SVG. Cela fonctionne bien sous Linux mais est insupportablement lent sur mes installations Windows 7 [MiKTeX sous Python (x, y) ou Anaconda], donc j'ai commencé à ajouter ce code, et les choses fonctionnent bien là-bas à nouveau:

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
la source