dlaczego kreślenie z Matplotlib jest tak wolne?

102

Obecnie oceniam różne biblioteki kreślące w języku Python. W tej chwili próbuję matplotlib i jestem dość rozczarowany wydajnością. Poniższy przykład jest zmodyfikowany na podstawie przykładów SciPy i daje mi tylko ~ 8 klatek na sekundę!

Jakieś sposoby na przyspieszenie tego, czy powinienem wybrać inną bibliotekę drukowania?

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)
ja sam
źródło
Odpowiednie mogą być następujące informacje: stackoverflow.com/questions/5003094/…
NPE
2
@aix - Glumpy pomógł tylko w tym przykładzie, ponieważ miał do czynienia z szybkim wyświetlaniem danych obrazu. W tym przypadku to nie pomoże.
Joe Kington
1
Spróbuj zmienić zaplecze. Zobacz moją odpowiedź: stackoverflow.com/a/30655528/2066079 . lub to często zadawane pytania dotyczące backendów
dberm22

Odpowiedzi:

116

Po pierwsze (chociaż nie zmieni to wcale wydajności) rozważ wyczyszczenie kodu, podobnie do tego:

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)

W powyższym przykładzie uzyskuję około 10 klatek na sekundę.

Krótka uwaga, w zależności od konkretnego przypadku użycia, matplotlib może nie być świetnym wyborem. Jest zorientowany na dane o jakości publikacyjnej, a nie wyświetlanie w czasie rzeczywistym.

Jest jednak wiele rzeczy, które możesz zrobić, aby przyspieszyć ten przykład.

Istnieją dwa główne powody, dla których jest to tak powolne.

1) Wywołanie fig.canvas.draw()przerysowuje wszystko . To twoje wąskie gardło. W twoim przypadku nie musisz ponownie rysować rzeczy, takich jak granice osi, etykiety znaczników itp.

2) W twoim przypadku jest wiele wątków pobocznych z wieloma etykietami tików. Te rysowanie zajmuje dużo czasu.

Oba można naprawić za pomocą blittingu.

Aby efektywnie korzystać z blittingu, musisz użyć kodu specyficznego dla zaplecza. W praktyce, jeśli naprawdę martwisz się płynnymi animacjami, i tak zazwyczaj osadzasz wykresy matplotlib w jakimś zestawie narzędzi GUI, więc nie stanowi to większego problemu.

Jednak nie wiedząc więcej o tym, co robisz, nie mogę Ci w tym pomóc.

Niemniej jednak istnieje sposób, w jaki można to zrobić, neutralny pod względem GUI, który jest nadal dość szybki.

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)

To daje ~ 200 fps.

Aby było to trochę wygodniejsze, animationsw najnowszych wersjach matplotlib dostępny jest moduł.

Jako przykład:

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
źródło
Twój kod jest rzeczywiście bardzo szybki, ale ostatecznie mam 2000 linii na oś! w jakiś sposób „line.set_ydata” tworzy nową linię zamiast ją aktualizować - czy może po prostu tło nie jest czyszczone? Poza tym, dlaczego twoja wersja jest o wiele szybsza? tylko dlatego, że porzuciłeś "draw ()" i zastąpiłeś go "ax.draw_artist"?
memyself
W którym przykładzie? (Przetestowałem je, ale możliwe, że skopiowałem i wkleiłem niewłaściwą wersję do odpowiedzi.) Ponadto, której wersji matplotlib używasz?
Joe Kington
4
tutaj jest łącze do powstałego obrazu i.imgur.com/aBRFz.png czy może to być artefakt spowodowany przez moją kartę graficzną?
memyself
7
Widziałem to samo, co ja sam widziałem w i.imgur.com/aBRFz.png, dopóki nie przeniosłem przechwytywania tła poniżej fig.show ().
Michael Browne,
4
Fajnie, ale animationwydaje się, że aktualizuje wykres według intervalokresu, co jeśli chcę go zaktualizować, gdy nowe dane są gotowe?
Alcott,
31

Matplotlib tworzy świetną grafikę o jakości publikacyjnej, ale nie jest zbyt dobrze zoptymalizowany pod kątem szybkości. Istnieje wiele pakietów do drukowania w Pythonie, które zostały zaprojektowane z myślą o szybkości:

Łukasz
źródło
1
Bardzo lubię pyqtgraph.org/documentation dla danych strumieniowych w czasie rzeczywistym. świetna robota luke
qrtLs
11

Na początek odpowiedź Joe Kingtona zawiera bardzo dobrą radę, stosując podejście neutralne pod względem gui i zdecydowanie powinieneś skorzystać z jego rady (szczególnie dotyczącej Blittingu) i zastosować ją w praktyce. Więcej informacji na temat tego podejścia można znaleźć w książce kucharskiej Matplotlib

Jednak podejście nie-neutralne dla GUI (obciążone GUI?) Jest kluczem do przyspieszenia kreślenia. Innymi słowy, zaplecze jest niezwykle ważne dla szybkości kreślenia.

Umieść te dwie linie, zanim zaimportujesz cokolwiek innego z matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Oczywiście zamiast tego można użyć różnych opcji GTKAgg, ale według wspomnianej wcześniej książki kucharskiej była to najszybsza. Zobacz link o backendach, aby uzyskać więcej opcji.

dberm22
źródło
Działa to jednak tylko w systemie Windows, czy znasz sposób, aby działał na komputerze Mac. Powodem, dla którego jest specyficzny dla systemu Windows, jest to, że pygtk jest specyficzny dla systemu Windows
user308827,
2
pygtk nie jest specyficzny dla systemu Windows. Właściwie to ogromny ból, aby działał pod Windowsem (jeśli to w ogóle możliwe, poddałem się).
Joseph Redfern,
7

W przypadku pierwszego rozwiązania zaproponowanego przez Joe Kingtona (.copy_from_bbox & .draw_artist & canvas.blit) musiałem uchwycić tła po linii fig.canvas.draw (), w przeciwnym razie tło nie miało żadnego efektu i otrzymałem taki sam wynik jak wspomniałeś. Jeśli umieścisz go po fig.show (), nadal nie będzie działał tak, jak zaproponował Michael Browne.

Więc po prostu umieść linię tła po płótnie.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]
Sebastian
źródło
4
powinieneś po prostu edytować jego odpowiedź, zamiast publikować ją jako osobną
endolith
1

Może to nie dotyczyć wielu z was, ale zwykle pracuję na moich komputerach pod Linuksem, więc domyślnie zapisuję moje wykresy w matplotlib jako PNG i SVG. Działa to dobrze pod Linuksem, ale jest nieznośnie powolne w moich instalacjach Windows 7 [MiKTeX pod Pythonem (x, y) lub Anaconda], więc zacząłem dodawać ten kod i wszystko działa dobrze:

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
źródło