在试图微调一些C / C ++函数的Python绑定中的一些内存泄漏时,我想到了一些与Numpy数组的垃圾收集有关的奇怪行为。
我创建了几个简化案例,以便更好地解释行为。代码使用memory_profiler
运行,后面的输出紧随其后。看来,当涉及到NumPy数组时,Python的垃圾收集工作不正常:
# File deallocate_ndarray.py
@profile
def ndarray_deletion():
import numpy as np
from gc import collect
buf = 'abcdefghijklmnopqrstuvwxyz' * 10000
arr = np.frombuffer(buf)
del arr
del buf
collect()
y = [i**2 for i in xrange(10000)]
del y
collect()
if __name__=='__main__':
ndarray_deletion()
使用以下命令,我调用了memory_profiler
:
python -m memory_profiler deallocate_ndarray.py
这就是我得到的:
Filename: deallocate_ndarray.py
Line # Mem usage Increment Line Contents
================================================
5 10.379 MiB 0.000 MiB @profile
6 def ndarray_deletion():
7 17.746 MiB 7.367 MiB import numpy as np
8 17.746 MiB 0.000 MiB from gc import collect
9 17.996 MiB 0.250 MiB buf = 'abcdefghijklmnopqrstuvwxyz' * 10000
10 18.004 MiB 0.008 MiB arr = np.frombuffer(buf)
11 18.004 MiB 0.000 MiB del arr
12 18.004 MiB 0.000 MiB del buf
13 18.004 MiB 0.000 MiB collect()
14 18.359 MiB 0.355 MiB y = [i**2 for i in xrange(10000)]
15 18.359 MiB 0.000 MiB del y
16 18.359 MiB 0.000 MiB collect()
我不明白为什么即使强制调用collect
也不会通过释放一些内存来减少程序的内存使用量。而且,即使Numpy数组由于底层C构造而不能正常运行,为什么列表(纯Python)不会被垃圾收集?
我知道del
没有直接调用基础__del__
方法,但您会注意到代码中的所有del
语句实际上最终减少了相应对象的引用计数为零(从而使它们有资格进行垃圾收集AFAIK)。通常,当对象进行垃圾回收时,我希望在增量列中看到负数条目。任何人都可以了解这里发生的事情吗?
注意:此测试在OS X 10.10.4,Python 2.7.10(conda),Numpy 1.9.2(conda),Memory Profiler 0.33(conda-binstar),psutil 2.2.1(conda)上运行。
答案 0 :(得分:3)
为了看到收集的内存垃圾,我不得不将buf的大小增加几个数量级。可能memory_profiler
的大小太小而无法检测到更改(它会查询操作系统,因此测量结果不是很精确),或者它太小而无法让Python垃圾收集器关注,我不知道。
例如,在因子buf
中将10000替换为100000000会产生
Line # Mem usage Increment Line Contents
================================================
21 10.289 MiB 0.000 MiB @profile
22 def ndarray_deletion():
23 17.309 MiB 7.020 MiB import numpy as np
24 17.309 MiB 0.000 MiB from gc import collect
25 2496.863 MiB 2479.555 MiB buf = 'abcdefghijklmnopqrstuvwxyz' * 100000000
26 2496.867 MiB 0.004 MiB arr = np.frombuffer(buf)
27 2496.867 MiB 0.000 MiB del arr
28 17.312 MiB -2479.555 MiB del buf
29 17.312 MiB 0.000 MiB collect()
30 17.719 MiB 0.406 MiB y = [i**2 for i in xrange(10000)]
31 17.719 MiB 0.000 MiB del y
32 17.719 MiB 0.000 MiB collect()