我一直在玩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()
并不起作用。 (也许是因为我们正在使用熊猫对象,面板和框架?我不知道。)
最令人困惑的部分是,我不会更改或修改f()
中的任何变量。它只是将一些列表表示副本放在本地列表中。因此python不需要复制任何东西。那么为什么以及如何发生这种情况呢?
=================
其他一些观察结果:
1)如果我用f()
(最后一行代码)调用f(panel.copy())
,传递副本而不是原始对象引用,我的内存使用结果完全不同: 。 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。
答案 0 :(得分:0)
这不是内存泄漏。您所看到的是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)