117 votes

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

J'évalue actuellement différentes bibliothèques de traçage python. Pour l'instant, j'essaie matplotlib et je suis assez déçu par les performances. L'exemple suivant a été modifié à partir de Exemples SciPy et me donne seulement ~ 8 images par seconde !

Y a-t-il un moyen d'accélérer le processus ou dois-je choisir une autre bibliothèque de traçage ?

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)

0 votes

Les éléments suivants pourraient être pertinents : stackoverflow.com/questions/5003094/

2 votes

@aix - Glumpy n'a été utile dans cet exemple que parce qu'il s'agissait d'un affichage rapide de données d'image. Il ne sera pas utile dans ce cas.

1 votes

Essayez de changer le backend. Voir ma réponse : stackoverflow.com/a/30655528/2066079 . ou la présente FAQ sur les backends : matplotlib.org/faq/usage_faq.html#what-is-a-backend

126voto

Joe Kington Points 68089

Tout d'abord, (bien que cela ne change pas du tout les performances), pensez à nettoyer votre code, de la manière suivante :

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 une petite note, selon votre cas d'utilisation exact, matplotlib peut ne pas être un excellent choix. Il est orienté vers les chiffres de qualité publication, pas vers l'affichage en temps réel.

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

Deux raisons principales expliquent cette lenteur.

1) 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 des tics, etc.

2) Dans votre cas, il y a beaucoup de sous-intrigues avec beaucoup d'étiquettes à cocher. 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 soucieux de la fluidité des animations, vous intégrez généralement les tracés de matplotlib dans une sorte de boîte à outils graphique, donc ce n'est pas un gros problème.

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

Néanmoins, il existe une manière neutre de le faire 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 ~200fps.

Pour rendre cela un peu plus pratique, il y a une fonction animations dans les versions récentes de matplotlib.

A titre d'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()

0 votes

Votre code est en effet très rapide, mais je me retrouve avec 2000 lignes par axe ! d'une certaine manière, "line.set_ydata" crée une nouvelle ligne au lieu de la mettre à jour - ou le fond n'est tout simplement pas effacé ? De plus, pourquoi votre version est-elle tellement plus rapide ? juste parce que vous avez abandonné "draw()" et l'avez remplacé par "ax.draw_artist" ?

0 votes

Dans quel exemple ? (Je les ai testés, mais il est possible que vous ayez copié-collé la mauvaise version dans la réponse). De plus, quelle version de matplotlib utilisez-vous ?

0 votes

Je veux parler du deuxième exemple. J'utilise matplotlib version 1.0.1.

35voto

Luke Points 4250

Matplotlib produit d'excellents graphiques de qualité professionnelle, mais n'est pas très bien optimisé pour la vitesse. Il existe une variété de paquets de traçage python qui sont conçus pour la vitesse :

1 votes

J'ai beaucoup apprécié pyqtgraph.org/documentation pour les données en temps réel. excellent travail luke

13voto

dberm22 Points 1370

Pour commencer, La réponse de Joe Kington fournit de très bons conseils en utilisant une approche neutre par rapport au gui, et vous devriez absolument suivre ses conseils (en particulier sur Blitting) et les mettre en pratique. Pour plus d'informations sur cette approche, lisez le Livre de recettes Matplotlib

Toutefois, l'approche non neutre par rapport à l'interface graphique est essentielle pour accélérer le traçage. En d'autres termes, l'approche backend est extrêmement important pour la vitesse du tracé.

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

import matplotlib
matplotlib.use('GTKAgg') 

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

0 votes

Cela ne fonctionne que sous 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.

2 votes

Pygtk n'est pas spécifique à Windows. En fait, il est très difficile de le faire fonctionner sous Windows (si c'est possible, j'ai abandonné).

9voto

Sebastian Points 16

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 celui que vous avez mentionné. Si vous le mettez après la fig.show(), cela ne fonctionne toujours pas comme le propose Michael Browne.

Donc, il suffit de mettre la ligne de fond 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]

5 votes

Vous devriez juste éditer sa réponse au lieu de la poster séparément

2voto

marisano Points 327

Cela ne s'applique peut-être pas à beaucoup d'entre vous, mais je fais généralement fonctionner mes ordinateurs sous Linux, et j'enregistre donc par défaut 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], j'ai donc ajouté ce code, et les choses fonctionnent bien là encore :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X