Python:无法复制内存使用率测试
我试图复制内存使用测试 本质上,文章声称,给定以下代码片段:Python:无法复制内存使用率测试,python,memory,memory-management,memory-profiling,Python,Memory,Memory Management,Memory Profiling,我试图复制内存使用测试 本质上,文章声称,给定以下代码片段: import copy import memory_profiler @profile def function(): x = list(range(1000000)) # allocate a big list y = copy.deepcopy(x) del x return y if __name__ == "__main__": function() 援引 python -m m
import copy
import memory_profiler
@profile
def function():
x = list(range(1000000)) # allocate a big list
y = copy.deepcopy(x)
del x
return y
if __name__ == "__main__":
function()
援引
python -m memory_profiler memory-profile-me.py
在64位计算机上打印
Filename: memory-profile-me.py
Line # Mem usage Increment Line Contents
================================================
4 @profile
5 9.11 MB 0.00 MB def function():
6 40.05 MB 30.94 MB x = list(range(1000000)) # allocate a big list
7 89.73 MB 49.68 MB y = copy.deepcopy(x)
8 82.10 MB -7.63 MB del x
9 82.10 MB 0.00 MB return y
我复制并粘贴了相同的代码,但是我的分析器产生了错误
Line # Mem usage Increment Line Contents
================================================
3 44.711 MiB 44.711 MiB @profile
4 def function():
5 83.309 MiB 38.598 MiB x = list(range(1000000)) # allocate a big list
6 90.793 MiB 7.484 MiB y = copy.deepcopy(x)
7 90.793 MiB 0.000 MiB del x
8 90.793 MiB 0.000 MiB return y
这篇文章可能已经过时了——探查器包或python可能已经改变了。无论如何,我的问题是,在Python3.6.x中
(1) copy.deepcopy(x)
(如上面代码中所定义)是否应该消耗大量内存
(2) 为什么我不能复制
(3) 如果我在del x
之后重复x=list(范围(1000000))
,内存是否会增加与我第一次分配的x=list(范围(1000000))
相同的数量(如代码的第5行)copy.deepcopy()
只递归复制可变对象,不复制整数或字符串等不可变对象。正在复制的列表由不可变整数组成,因此y
副本最终共享对相同整数值的引用:
>>> import copy
>>> x = list(range(1000000))
>>> y = copy.deepcopy(x)
>>> x[-1] is y[-1]
True
>>> all(xv is yv for xv, yv in zip(x, y))
True
因此,复制只需要创建一个包含100万个引用的新列表对象,这个对象在Mac OS X 10.13(64位操作系统)上构建的Python 3.6上占用略多于8MB的内存:
空的列表
对象需要64个字节,每个引用需要8个字节:
>>> sys.getsizeof([])
64
>>> sys.getsizeof([None])
72
Python列表对象过度分配了增长空间,将range()
对象转换为列表会使其比使用deepcopy
时腾出更多的空间进行额外增长,因此x
更大一些,在再次调整大小之前可以容纳额外的125k对象:
>>> sys.getsizeof(x)
9000112
>>> sys.getsizeof(x) / 2 ** 20
8.583175659179688
>>> ((sys.getsizeof(x) - 64) // 8) - 10**6
125006
虽然复印件只有大约87k的额外空间:
>>> ((sys.getsizeof(y) - 64) // 8) - 10**6
87175
在Python3.6上,我也不能复制文章所说的内容,部分原因是Python已经看到了很多内存管理的改进,部分原因是文章在几个方面是错误的
copy.deepcopy()
关于列表和整数的行为在copy.deepcopy()
的漫长历史中从未改变过(请参见),即使在Python 2.7上,内存图的解释也是错误的
具体来说,我可以使用Python 2.7重现结果这是我在机器上看到的:
$ python -V
Python 2.7.15
$ python -m memory_profiler memtest.py
Filename: memtest.py
Line # Mem usage Increment Line Contents
================================================
4 28.406 MiB 28.406 MiB @profile
5 def function():
6 67.121 MiB 38.715 MiB x = list(range(1000000)) # allocate a big list
7 159.918 MiB 92.797 MiB y = copy.deepcopy(x)
8 159.918 MiB 0.000 MiB del x
9 159.918 MiB 0.000 MiB return y
现在发生的事情是Python的内存管理系统正在分配一个新的内存块以进行额外的扩展。这并不是说新的y
list对象占用了将近93MiB的内存,这只是当Python进程为对象堆请求更多内存时,操作系统分配给该进程的额外内存。列表对象本身要小得多
对于实际发生的情况,该方法更加准确:
python3 -m memory_profiler --backend tracemalloc memtest.py
Filename: memtest.py
Line # Mem usage Increment Line Contents
================================================
4 0.001 MiB 0.001 MiB @profile
5 def function():
6 35.280 MiB 35.279 MiB x = list(range(1000000)) # allocate a big list
7 35.281 MiB 0.001 MiB y = copy.deepcopy(x)
8 26.698 MiB -8.583 MiB del x
9 26.698 MiB 0.000 MiB return y
Python3.x内存管理器和列表实现比2.7中的实现更智能;显然,新的列表对象能够放入现有的可用内存中,这些内存是在创建x
时预先分配的
我们可以使用a和a测试Python2.7的行为。现在,我们在Python 2.7上也获得了更令人放心的结果:
Filename: memtest.py
Line # Mem usage Increment Line Contents
================================================
4 0.099 MiB 0.099 MiB @profile
5 def function():
6 31.734 MiB 31.635 MiB x = list(range(1000000)) # allocate a big list
7 31.726 MiB -0.008 MiB y = copy.deepcopy(x)
8 23.143 MiB -8.583 MiB del x
9 23.141 MiB -0.002 MiB return y
我注意到作者也感到困惑:
copy.deepcopy
复制两个列表,再次分配~50MB(我不确定50MB-31MB=19MB的额外开销是从哪里来的)
(我的粗体强调)
这里的错误是假设Python进程大小中的所有内存更改都可以直接归因于特定对象,但实际情况要复杂得多,因为内存管理器可以根据需要添加(和删除!)内存“竞技场”,即为堆保留的内存块,如果有意义的话,可以在更大的块中这样做。这里的过程是复杂的,因为它取决于。作者发现了一篇关于Python模型的老文章,他们把它误解为最新的;从Python2.5开始,Python不释放内存的说法不再成立
令人不安的是,同样的误解导致作者建议不要使用pickle
,但实际上,即使在Python 2上,该模块也只添加了一点簿记内存来跟踪递归结构。看见在Python2.7上使用cPickle
会一次性增加46MiB(将create_file()
调用增加一倍不会导致内存进一步增加)。在Python3中,内存更改完全消失了
我将与Theano团队就这篇文章打开一个对话,这篇文章是错误的,令人困惑的,而且Python 2.7很快就会完全过时,所以他们真的应该关注Python 3的内存模型。(*)
当您从range()
创建一个新列表(而不是副本)时,您将看到与第一次创建x
类似的内存增加,因为除了新的列表对象之外,您还将创建一组新的整数对象。除此之外,Python不会为range()
操作缓存和重用整数值
(*)附录:我是以萨诺项目开始的。项目同意我的评估,尽管他们还没有更新发布的版本。第三个问题可以通过自己尝试并查看results@user3483203我不确定包裹是否给了我正确的答案。文件中清楚地说明“请注意,在不同的平台上或使用不同的python版本时,可能会得到不同的结果。“我认为期望相同的结果是不现实的。答案很好!后续问题:当您使用Python3时,为什么在使用DelX时只减少8mb的内存使用量?这是否意味着为
x
分配的内存仍然不可用?@tryingtosolve:因为Python使用引用计数来跟踪可以释放的内存。删除x
将删除对两个列表对象之一的最后一个引用,每个列表对象都包含~8MB的8字节引用。这两个列表分别引用了相同的100万个整数对象,在x引用的列表之后
Filename: memtest.py
Line # Mem usage Increment Line Contents
================================================
4 0.099 MiB 0.099 MiB @profile
5 def function():
6 31.734 MiB 31.635 MiB x = list(range(1000000)) # allocate a big list
7 31.726 MiB -0.008 MiB y = copy.deepcopy(x)
8 23.143 MiB -8.583 MiB del x
9 23.141 MiB -0.002 MiB return y