【发布时间】: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