Почему заговор с 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)

5 ответов

Решение

Во-первых, (хотя это совсем не изменит производительность) рассмотрите возможность очистки кода, например:

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

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

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

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

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

import matplotlib
matplotlib.use('GTKAgg') 

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

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

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