чому загортання з Matplotlib таке повільне?


100

В даний час я оцінюю різні бібліотеки побудови графіків python. Зараз я намагаюся мати matplotlib, і я дуже розчарований роботою. Наступний приклад модифікований із прикладів SciPy і дає мені лише ~ 8 кадрів на секунду!

Будь-які способи пришвидшити це, чи варто вибрати іншу бібліотеку для побудови графіків?

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)

Наступне може бути доречним: stackoverflow.com/questions/5003094/…
NPE

2
@aix - Глампі лише допоміг у цьому прикладі, оскільки мав справу із швидким відображенням даних зображень. У цьому випадку це не допоможе.
Джо Кінгтон,

1
Спробуйте змінити бекенд. Дивіться мою відповідь: stackoverflow.com/a/30655528/2066079 . або цей FAQ про backends: matplotlib.org/faq/usage_faq.html#what-is-a-backend
dberm22

Відповіді:


115

По-перше, (хоча це зовсім не змінить продуктивність), розгляньте можливість очищення коду, подібно до цього:

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)

У наведеному вище прикладі я отримую близько 10 кадрів в секунду.

Просто коротко, залежно від вашого конкретного випадку використання, matplotlib може бути не найкращим вибором. Він орієнтований на показники якості публікації, а не на показ у режимі реального часу.

Однак є багато речей, які ви можете зробити, щоб пришвидшити цей приклад.

Є дві основні причини, чому це так само повільно, як і зараз.

1) Телефонування fig.canvas.draw()перерисовує все . Це ваше вузьке місце. У вашому випадку вам не потрібно перемальовувати такі речі, як межі осей, позначки галочок тощо.

2) У вашому випадку є багато підзаголовків із великою кількістю міток. Вони малюють довго.

І те, і інше можна виправити за допомогою бліттингу.

Щоб ефективно виконувати блітінг, вам доведеться використовувати специфічний код. На практиці, якщо ви дійсно стурбовані плавною анімацією, ви, як правило, вбудовуєте графіки matplotlib в якийсь набір інструментів gui, тому це не велика проблема.

Однак, не знаючи трохи більше про те, що ви робите, я не можу вам там допомогти.

Тим не менше, існує нейтральний до графічного інтерфейсу спосіб, який все ще є досить швидким.

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)

Це дає мені ~ 200 кадрів в секунду.

Щоб зробити це трохи зручнішим, є animations в останніх версіях matplotlib модуль.

Як приклад:

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

ваш код справді дуже швидкий, однак у підсумку я отримую 2000 рядків на вісь! якимось чином "line.set_ydata" створює новий рядок замість того, щоб оновлювати його - або фон просто не очищається? Крім того, чому ваша версія набагато швидша? тільки тому, що ви випустили "draw ()" і замінили його на "ax.draw_artist"?
memyself

У якому прикладі? (Я протестував їх, але можливо, скопіювавши неправильну версію у відповідь.) Крім того, яку версію matplotlib ви використовуєте?
Джо Кінгтон,

4
ось посилання на отримане зображення i.imgur.com/aBRFz.png це може бути артефактом, спричиненим моєю графічною картою?
memyself

7
Я бачив те саме, що бачив memyself у i.imgur.com/aBRFz.png, поки не перемістив фонове захоплення під fig.show ().
Michael Browne

4
Приємно, але, animationсхоже, оновлення сюжету за intervalперіодом часу, а що, якщо я просто хочу його оновити, коли нові дані будуть готові?
Alcott

28

Matplotlib робить чудову графіку якісної публікації, але не дуже оптимізовану для швидкості. Існує безліч пакунків для побудови пітонів, розроблених з урахуванням швидкості:


1
Я дуже насолоджуюсь pyqtgraph.org/documentation для потокових даних у режимі реального часу. great job luke
qrtLs

11

Для початку у відповіді Джо Кінгтона є дуже хороша порада з використанням нейтрального підходу, і ви неодмінно повинні скористатися його порадою (особливо щодо Blitting) і застосувати її на практиці. Більше інформації про цей підхід читайте у посібнику Matplotlib Cook Cook

Однак не нейтральний до графічного інтерфейсу (GUI-упереджений?) Підхід є ключовим для пришвидшення побудови графіків. Іншими словами, бекенд надзвичайно важливий для побудови графіку швидкості.

Поставте ці два рядки перед тим, як імпортувати щось інше з matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Звичайно, замість них можна використовувати різні варіанти GTKAgg, але згідно згаданої книги поварів це було найшвидшим. Щоб отримати додаткові варіанти, перегляньте посилання про бекенди.


Це працює лише у вікнах, чи знаєте ви, як зробити так, щоб це працювало на Mac. Причиною, що стосується Windows, є те, що pygtk - це специфікація Windows
user308827

2
pygtk не є специфічним для Windows. Насправді це дуже боляче, коли це працює під Windows (якщо це можливо, я відмовився.)
Джозеф Редферн,

7

Для першого рішення, запропонованого Джо Кінгтоном (.copy_from_bbox & .draw_artist & canvas.blit), мені довелося захопити фони після рядка fig.canvas.draw (), інакше фон не мав ефекту, і я отримав той самий результат, що і ви згадали. Якщо ви ставите його після fig.show (), він все одно не працює, як запропонував Майкл Браун.

Тож просто поставте фонову лінію після 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]

4
вам слід просто відредагувати його відповідь, а не публікувати як окрему
ендоліт

1

Це може не стосуватися багатьох з вас, але я, як правило, працюю на своїх комп'ютерах під Linux, тому за замовчуванням я зберігаю свої графіки matplotlib як PNG та SVG. Це чудово працює під Linux, але нестерпно повільно працює на моїх установках Windows 7 [MiKTeX під Python (x, y) або Anaconda], тому я взяв додавання цього коду, і все знову працює нормально:

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')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.