Почему заговор с Matplotlib так медленно?

В настоящее время я оцениваю различные библиотеки построения 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)

Ответ 1

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

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) В вашем случае есть много подзаговоров с большим количеством тиковых ярлыков. Это займет много времени.

Оба они могут быть исправлены с помощью blitting.

Чтобы эффективно использовать blitting, вам нужно будет использовать код, специфичный для бэкэнд. На практике, если вы действительно беспокоитесь о гладкой анимации, вы обычно встраиваете графики matplotlib в какой-то набор инструментов gui, так что это не большая проблема.

Однако, не зная немного больше о том, что вы делаете, я не могу вам помочь.

Тем не менее, существует 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 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 кадров в секунду.

Чтобы сделать это немного более удобным, в последних версиях matplotlib есть модуль animations.

В качестве примера:

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

Ответ 2

Matplotlib делает отличную графику качества публикации, но не очень оптимизирован для скорости. Существует множество пакетов для построения питона, которые разработаны с учетом скорости:

Ответ 3

Чтобы начать, ответ Джо Кингтона дает очень хорошие советы, используя нейтральный подход, и вы должны обязательно принять его совет (особенно о Blitting) и положить его в практика. Подробнее об этом подходе читайте Поваренная книга Matplotlib

Однако подход, не ориентированный на GUI (GUI-предвзятый?), является ключом к ускорению построения графика. Другими словами, backend является чрезвычайно важным для построения скорости.

Поместите эти две строки, прежде чем импортировать что-нибудь еще из matplotlib:

import matplotlib
matplotlib.use('GTKAgg') 

Конечно, есть несколько вариантов использования вместо GTKAgg, но в соответствии с упомянутой ранее кулинарной книгой это было самым быстрым. Дополнительную информацию см. В ссылке на бэкэнд.

Ответ 4

Для первого решения, предложенного Joe Kington (.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 capture the background of the figure
backgrounds = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axes]

Ответ 5

Это может не относиться ко многим из вас, но я обычно управляю своими компьютерами под 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')