try*_*lve 4 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()
Run Code Online (Sandbox Code Playgroud)
调用
python -m memory_profiler memory-profile-me.py
Run Code Online (Sandbox Code Playgroud)
在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
Run Code Online (Sandbox Code Playgroud)
我复制并粘贴了相同的代码,但我的分析器产生了
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
Run Code Online (Sandbox Code Playgroud)
这篇文章可能已经过时了 - 无论是profiler包还是python都可能已经改变了.无论如何,我的问题是,在Python 3.6.x中
(1)应该copy.deepcopy(x)
(如上面的代码中所定义)消耗大量的内存吗?
(2)为什么我不能复制?
(3)如果我重复x = list(range(1000000))
之后del x
,内存是否会增加与我第一次分配的量相同x = list(range(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
Run Code Online (Sandbox Code Playgroud)
因此,副本只需要创建一个包含100万个引用的新列表对象,这个对象在Mac OS X 10.13(64位操作系统)上的Python 3.6构建上占用8MB以上的内存:
>>> import sys
>>> sys.getsizeof(y)
8697464
>>> sys.getsizeof(y) / 2 ** 20 # Mb
8.294548034667969
Run Code Online (Sandbox Code Playgroud)
空list
对象占用64个字节,每个引用占用8个字节:
>>> sys.getsizeof([])
64
>>> sys.getsizeof([None])
72
Run Code Online (Sandbox Code Playgroud)
Python列表对象过度分配空间以进行增长,将range()
对象转换为列表会使其为使用时的额外增长腾出更多空间deepcopy
,因此x
稍微大一点,在再次调整大小之前还有额外125k对象的空间:
>>> sys.getsizeof(x)
9000112
>>> sys.getsizeof(x) / 2 ** 20
8.583175659179688
>>> ((sys.getsizeof(x) - 64) // 8) - 10**6
125006
Run Code Online (Sandbox Code Playgroud)
虽然副本只有大约87k的剩余空间:
>>> ((sys.getsizeof(y) - 64) // 8) - 10**6
87175
Run Code Online (Sandbox Code Playgroud)
在Python 3.6上我也无法复制文章声明,部分原因是因为Python已经看到了很多内存管理改进,部分原因是文章在几个方面都是错误的.
copy.deepcopy()
关于列表和整数的行为在历史悠久的历史中从未改变copy.deepcopy()
(参见1995年增加的模块的第一个版本),并且对内存数字的解释是错误的,即使在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
Run Code Online (Sandbox Code Playgroud)
发生的事情是Python的内存管理系统正在为新的扩展分配一大块内存.并不是新的y
列表对象占用了近93MiB的内存,这只是当该进程为对象堆请求更多内存时操作系统分配给Python进程的额外内存.该列表对象本身是很多小.
在Python 3的tracemalloc
模块是一个很多关于到底发生了什么更准确:
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
Run Code Online (Sandbox Code Playgroud)
Python 3.x内存管理器和列表实现比2.7中的那些更聪明; 显然,新的列表对象能够适应现有的已有内存,在创建时预先分配x
.
我们可以使用手动构建的Python 2.7.12 tracemalloc二进制文件和一个小补丁来memory_profile.py
测试Python 2.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
Run Code Online (Sandbox Code Playgroud)
我注意到作者也很困惑:
copy.deepcopy
复制两个列表,再次分配~50 MB(我不知道50 MB的额外开销 - 31 MB = 19 MB来自哪里)
(大胆强调我的).
这里的错误是假设Python进程大小中的所有内存更改都可以直接归因于特定对象,但实际情况要复杂得多,因为内存管理器可以添加(并删除!)内存'arenas',内存块根据需要为堆保留,如果有意义,将在更大的块中保留.这里的过程很复杂,因为它取决于Python管理器和操作系统malloc
实现细节之间的交互.作者发现了一篇关于Python模型的旧文章,他们误解了这篇文章是最新的,该文章的作者自己已经试图指出这一点 ; 从Python 2.5开始,Python不释放内存的说法不再适用.
令人不安的是,同样的误解导致作者建议不要使用pickle
,但实际上,即使在Python 2上,模块也不会添加超过一点的簿记内存来跟踪递归结构.请参阅此要点以了解我的测试方法 ; cPickle
在Python 2.7上使用会增加一次46MiB的增加(create_file()
调用结果加倍,不再增加内存).在Python 3中,内存的变化完全消失了.
我将打开一个与Theano团队关于帖子的对话,文章是错误的,令人困惑,并且Python 2.7很快就会被完全淘汰,所以他们真的应该专注于Python 3的内存模型.(*)
当你创建一个新的列表,从range()
,不是副本,你会看到在内存中有类似的增加作为创建x
的第一次,因为你除了新的列表对象创建一组新的整数对象.除了一组特定的小整数外,Python不会为range()
操作缓存和重用整数值.
(*) 附录:我用Thano项目打开了#6619号问题.该项目同意我的评估,并从他们的文档中删除了该页面,尽管他们尚未更新已发布的版本.
归档时间: |
|
查看次数: |
162 次 |
最近记录: |