为什么用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-Glumpy仅在该示例中有所帮助,因为他正在处理快速显示的图像数据。在这种情况下没有帮助。
乔·肯顿

1
尝试更改后端。看到我的答案:stackoverflow.com/a/30655528/2066079。或以下有关后端的常见问题解答:matplotlib.org/faq/usage_faq.html#what-is-a-backend
dberm22 2015年

Answers:


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)

在上面的示例中,我得到了大约10fps。

简要说明一下,根据您的实际使用情况,matplotlib可能不是一个不错的选择。它面向的是出版物质量的数字,而不是实时显示。

但是,您可以做很多事情来加快此示例的速度。

速度如此之慢的主要原因有两个。

1)调用会重fig.canvas.draw()所有内容。这是您的瓶颈。就您而言,您无需重新绘制诸如轴边界,刻度线标签等内容。

2)在您的情况下,有很多带有很多刻度标签的子图。这些需要很长时间才能绘制出来。

这些都可以通过使用blitting来解决。

为了高效地进行blit,您必须使用特定于后端的代码。在实践中,如果您真的担心平滑的动画,那么无论如何,通常都将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)

这给了我约200fps。

为了使此操作更加方便,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 2012年

在哪个例子中?(我测试了它们,但是有可能将错误的版本复制粘贴到答案中。)而且,您使用的是matplotlib的哪个版本?
乔·肯顿2012年

4
这是结果图像的链接i.imgur.com/aBRFz.png这可能是由我的显卡引起的伪影吗?
memyself 2012年

7
我一直在i.imgur.com/aBRFz.png中看到与我自己一样的东西,直到将背景捕获移到fig.show()下面
迈克尔·布朗

4
很好,但是animation似乎可以按interval时间段更新图表,如果我只想在准备好新数据后更新图表,该怎么办?
Alcott 2014年


11

首先,乔·肯顿(Joe Kington)的答案使用gui-neutral方法提供了很好的建议,您绝对应该接受他的建议(尤其是关于Blitting的建议)并将其付诸实践。有关此方法的更多信息,请阅读Matplotlib Cookbook

但是,非GUI中立(GUI偏向?)方法是加快绘图速度的关键。换句话说,后端对于绘制速度极为重要。

从matplotlib导入其他任何内容之前,请先放置以下两行:

import matplotlib
matplotlib.use('GTKAgg') 

当然,可以使用多种选项代替GTKAgg,但是根据前面提到的菜谱,这是最快的。有关更多选项,请参见有关后端的链接。


不过,这仅适用于Windows,您知道一种使其在Mac上运行的方法。它是Windows特定的原因是pygtk是Windows特定的
user308827

2
pygtk不是Windows特有的。实际上,要使其在Windows下运行非常痛苦(如果可能的话,我已经放弃了。)
Joseph Redfern

7

对于Joe Kington提出的第一个解决方案(.copy_from_bbox&.draw_artist&canvas.blit),我必须在fig.canvas.draw()行之后捕获背景,否则背景无效,并且得到与你提到过 如果将它放在fig.show()之后,它仍然不能像Michael Browne所建议的那样工作。

所以只要把背景线的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
您应该只编辑他的答案,而不是单独发布一个答案
endolith 2013年

1

这可能不适用于许多人,但是我通常在Linux下操作计算机,因此默认情况下,我将matplotlib图保存为PNG和SVG。这在Linux上可以正常工作,但在Windows 7安装中(Python(x,y)或Anaconda下的MiKTeX)运行起来却慢得令人难以忍受,因此我开始添加此代码,并且在那之后一切正常:

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')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.