Ipython: 内联%matplotlib的内存泄漏

创建于 2014-12-19  ·  23评论  ·  资料来源: ipython/ipython

嘿大家

我发现了一个问题。 只需启动代码并查看内存即可。 然后删除“%matplotlib inline”并再次启动。

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker

%matplotlib inline

import os
import sys
import StringIO
import urllib, base64

from matplotlib import rcParams

rcParams['figure.figsize'] = (24, 6)
rcParams['figure.dpi'] = 150

OUTPUT_FILENAME = "Asd"

def printHTML(html):
    with open(OUTPUT_FILENAME, "a") as outputFile: outputFile.write(html if type(html) == str else html.encode('utf8') )

def friendlyPlot():

    figure = plt.Figure()
    ax = plt.subplot2grid((1,2), (0,0))

    ax.plot( range(1000), range(1000) )


    #plt.show() 
    fig = plt.gcf()

    imgdata = StringIO.StringIO()
    fig.savefig(imgdata, format='png')
    imgdata.seek(0)  # rewind the data
    image = imgdata.buf.encode('base64').replace('\n', '')
    printHTML('<img src="data:image/png;base64,{0}" /><br />'.format(image))
    plt.close('all')
    imgdata.close()

open(OUTPUT_FILENAME, 'w').close()

for i in range(500):
    friendlyPlot()
bug matplotlib

最有用的评论

我将第二次对此问题进行修复,将不胜感激。

所有23条评论

我也遇到了这个错误,有没有办法获得没有内存泄漏的内联图? 我不想为每个图启动单独的过程,因为数组很大。

当内存使用量增加时,您能检查一下吗:

len(IPython.kernel.zmq.pylab.backend_inline.show._to_draw)

那是存储数字的列表。 他们应该只是暂时存在,但也许他们没有被清除就正在建立。

len(IPython.kernel.zmq.pylab.backend_inline.show._to_draw)= 0

顺便说一句,我正在使用.plot()方法绘制熊猫数据帧。

好的,对于那个理论来说就这么多了。

大熊猫也可能会在内部围绕地块保留一些数据。 不过,原始报告不涉及熊猫。

每个额外的图看起来要增加多少内存?

好的,这似乎是我的情况,我使用的是熊猫0.16.0,但问题已在master中修复:

https://github.com/pydata/pandas/pull/9814

十分感谢。 由于原始报告不涉及熊猫,因此不公开。

这可以更简单地复制:

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker

%matplotlib inline

import os
import sys
import StringIO
import urllib, base64

from matplotlib import rcParams

rcParams['figure.figsize'] = (24, 6)
rcParams['figure.dpi'] = 150



def friendlyPlot():
    fig, ax = plt.subplots()
    ax.plot(range(1000))
    fig.savefig('tmp.png')
    plt.close('all')


for i in range(500):
    friendlyPlot()

这不会泄漏内存,所以它在IPython方面而不是pyplot方面(我认为)。

import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import matplotlib.ticker



import os
import sys
import StringIO
import urllib, base64

from matplotlib import rcParams

rcParams['figure.figsize'] = (24, 6)
rcParams['figure.dpi'] = 150



def friendlyPlot():
    fig, ax = plt.subplots()
    ax.plot(range(1000))
    fig.savefig('tmp.png')
    plt.close('all')


for i in range(500):
    friendlyPlot()

@tacaswell在您的测试代码中,Windows 7上的IPython消耗了大约1.7GB,此后未释放。 以稍高的迭代次数运行会导致内存错误。 因此,这仍然是一个问题。

@asteppke第一个或第二个块?

@tacaswell第一个测试代码( %matplotlib inline )的内存消耗将达到1.7GB。 相反,使用第二部分( matplotlib.use('agg') )时,内存使用量仅在50MB和100MB之间振荡。

这两个测试都是使用Python 3.4和IPython Notebook版本4.0.5执行的。

我已经玩了更多。 我注意到,如果我在@tacaswell的示例中重新运行for循环几次,则内存使用不会增加-这似乎与您在单个单元格中创建的数量很重要。 IPython当然会保留该单元格中为内联后端生成的所有图形的列表,但是在运行该单元格后,肯定会清除该列表,即使执行gc.collect()也不会使内存使用率下降。

我们的代码可能与matplotlib中的某些东西进行严重的交互吗? 我以为_pylab_helpers.Gcf看起来很可能,但似乎没有保留任何东西。

我试图抓住其中一个数字的引用,并在其上调用gc.get_referrers() ; 除了我在user_ns中拥有的引用以外,所有其他引用都看起来像mpl对象-大概其中许多处于引用循环中。 什么对象最有可能是其他内容不恰当地保留引用?

我将其放入里程碑“愿望清单”中。 我们想修复它,但是目前我们不确定如何在识别该错误方面取得进一步的进展,我认为不值得为此保留版本。

任何可以进步的人都会得到布朗尼积分。 还蛋糕。

并不是真正的进步,但是内存似乎在内核内部的某个地方丢失了。 在循环后或循环内调用gc.collect()都没有帮助,并且summary.print_(summary.summarize(muppy.get_objects()))找不到任何泄漏的内存。 也不会将所有_N_iNNone帮助。 真的很神秘。

我也想知道它是否正在创建无法收集的对象,但是当没有其他引用时,它们应该以gc.garbage结尾,并且当我看到它用尽RAM负载时,它仍然为空。

我认为知道这些事情的人将不得不使用C级工具来跟踪未释放的内存。 没有证据表明我们可以找到任何地方保留额外的Python对象。

我将第二次对此问题进行修复,将不胜感激。

我们知道,但是目前还没有人找出导致该错误的原因。

+1

+1

顺便说一句,我仍然不时在最新的matplotlib,熊猫,jupyter和ipython上遇到这个问题。 如果有人知道可以帮助解决此多进程通信问题的调试器,请告诉我。

可能与浏览器缓存机制有关吗?

好主意,但我不这么认为。 这是IPython占用额外内存的过程,而不是浏览器,并且
@tacaswell的复制不涉及将情节发送到浏览器。

嗨,我相信我已经找到了罪魁祸首的一部分,并且是一种可以(但不是完全)减轻这一问题的方法!

滚动浏览ipykernel/pylab/backend_inline.py代码后,我直觉交互模式会大量存储“ plot-things”,尽管我并不完全了解它,所以我无法查明确切的原因确定无疑。

这是验证此代码的代码(基于上面的@tacaswell的片段),对于尝试实施此修复程序的任何人都非常有用。

初始化:

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker

%matplotlib inline

matplotlib.rcParams['figure.figsize'] = (24, 6)
matplotlib.rcParams['figure.dpi'] = 150

from resource import getrusage
from resource import RUSAGE_SELF

def friendlyPlot():
    fig, ax = plt.subplots()
    ax.plot(range(1000))
    fig.savefig('tmp.png')
    plt.close('all')

实际测试:

print("before any:  {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))
friendlyPlot()
print("before loop: {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))
for i in range(50):
    friendlyPlot()
print("after loop:  {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))
import gc ; gc.collect(2)
print("after gc:    {:7d} kB".format(getrusage(RUSAGE_SELF).ru_maxrss))

运行它的50次循环迭代,我得到:

before any:    87708 kB
before loop:  106772 kB
after loop:   786668 kB
after gc:     786668 kB

将其运行200次循环,得到:

before any:    87708 kB
before loop:  100492 kB
after loop:  2824316 kB
after gc:    2824540 kB

这显示了随着迭代内存的线性增长。

现在解决问题/解决方法:在测试代码片段之前调用matplotlib.interactive(False) ,然后运行它。

经过50次迭代:

before any:    87048 kB
before loop:  104992 kB
after loop:   241604 kB
after gc:     241604 kB

并进行200次迭代:

before any:    87536 kB
before loop:  103104 kB
after loop:   239276 kB
after gc:     239276 kB

这证实只剩下恒定的增量(与迭代无关)。

使用这些数字,我可以粗略估计每次迭代的泄漏大小:

(786668-(241604 - 104992))/50   = 13001.12
(2824316-(241604 - 104992))/200 = 13438.52

对于循环的单次迭代,我得到13560 。 因此,无论是原始(> 3MB)还是png压缩(54KB),每次迭代的泄漏

而且,奇怪的是,在同一单元中重复运行小规模测试(仅几次迭代)而没有重新启动内核的一致性要差得多,我无法理解这一点或确定模式。

我希望对内部结构有更多了解的人可以从这里开始学习,因为我现在没有时间和知识来深入研究它。

有用

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

ghost picture ghost  ·  4评论

quchunguang picture quchunguang  ·  3评论

okomarov picture okomarov  ·  3评论

gregcaporaso picture gregcaporaso  ·  3评论

RinaldoB picture RinaldoB  ·  3评论