【问题标题】:Memory is not released after function calls in python在python中调用函数后内存没有释放
【发布时间】:2017-06-04 21:02:55
【问题描述】:

我和memory_profiler玩了一段时间,从下面的小程序中得到了这个有趣但令人困惑的结果:

import pandas as pd
import numpy as np

@profile
def f(p):
    tmp = []
    for _, frame in p.iteritems():
        tmp.append([list(record) for record in frame.to_records(index=False)])

# initialize a list of pandas panels
lp = []
for j in xrange(50):
    d = {}
    for i in xrange(50):
        df = pd.DataFrame(np.random.randn(200, 50))
        d[i] = df
    lp.append(pd.Panel(d))

# execution (iteration)
for panel in lp:
    f(panel)

然后如果我使用 memory_profiler 的 mprof 来分析运行时的内存使用情况,mprof run test.py 没有任何其他参数,我得到这个: 。

每次函数调用 f() 后似乎都有内存未释放。

tmp 只是一个本地列表,每次调用 f() 时都应该重新分配和重新分配内存。显然,所附图表中存在一些差异。我知道 python 有自己的内存管理块,也有 int 和其他类型的空闲列表,gc.collect() 应该会变魔术。事实证明,显式 gc.collect() 不起作用。 (也许是因为我们正在使用 pandas 对象、面板和框架?我不知道。)

最令人困惑的部分是,我没有更改或修改f() 中的任何变量。它所做的只是将一些列表表示副本放在本地列表中。因此python不需要复制任何东西。那么为什么以及如何发生这种情况呢?

==================

其他一些观察:

1) 如果我用f(panel.copy())(最后一行代码)调用f(),传递副本而不是原始对象引用,我会得到完全不同的内存使用结果:。 python 是否聪明地告诉这个传递的值是一个副本,以便它可以在每次函数调用后做一些内部技巧来释放内存?

2) 我想可能是因为df.to_records()。好吧,如果我将其更改为frame.values,我会在迭代期间获得类似的平坦内存曲线,就像上面显示的memory_profiling_results_2.png(尽管我确实需要to_records(),因为它维护了列dtype,而.values 则混淆了dtypes了)。但我在to_records() 上查看了frame.py 的实现。我不明白为什么它会保留内存,而 .values 可以正常工作。

我在 Windows 上运行该程序,使用 python 2.7.8、memory_profiler 0.43 和 psutil 5.0.1。

【问题讨论】:

  • 感谢@StephenRauch 指出这一点。对此的更新:这是由于在 pandas 中缓存数据帧。当调用__getitem__() 访问数据帧的列时,每列将存储到_item_cache。在这种情况下,这是因为pd.to_records() 有一个列表理解,其中包含self[c] for ...。实际上,所有数据帧都在调用后被缓存。
  • 有什么办法可以避免缓存或变通?

标签: python pandas memory garbage-collection


【解决方案1】:

这不是内存泄漏。您看到的是pandas.core.NDFrame 缓存一些结果的副作用。这允许它在您第二次请求时返回相同的信息,而无需再次运行计算。将示例代码的末尾更改为类似于以下代码并运行它。你应该会发现,第二次通过内存增加不会发生,执行时间会更少。

import time

# execution (iteration)
start_time = time.time()
for panel in lp:
    f(panel)
print(time.time() - start_time)

print('-------------------------------------')
start_time = time.time()
for panel in lp:
    f(panel)
print(time.time() - start_time)

【讨论】:

  • 先谢谢你的回答!是的,我也尝试过,并且知道这不是泄漏的事实!你说这是缓存的副作用。你知道缓存发生在哪里吗?或者怎么做?因为我做了一些类似的实验,结果发现缓存并不是每次都发生。例如,如果我使用.values 而不是to_records(),我不会看到内存增加。您能否指出在 pandas 核心中实现缓存的位置?或者简单地向我解释缓存何时会进入我们的方式?有什么办法可以解决吗?使用.copy()?
  • 我需要“修复”这个问题的原因是,如果我有一个更长的列表并且lp 现在包含 500 个对象,那么内存会增加太多。
  • 使用调试器(我使用pycharm),您可以进入pandas 代码,看看它是如何工作的。
猜你喜欢
  • 2011-12-10
  • 2012-08-13
  • 2016-07-19
  • 2012-10-10
  • 1970-01-01
  • 2019-01-27
  • 1970-01-01
  • 2018-08-20
  • 2017-06-07
相关资源
最近更新 更多