python - Python:无法复制内存使用情况的测试

标签 python memory memory-management memory-profiling

我试图复制内存使用率测试here

从本质上讲,该帖子声称给出了以下代码片段:

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可能已更改。无论如何,我的问题是在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


因此,该副本仅需要创建一个具有100万个引用的新列表对象,该对象在Mac OS X 10.13(64位OS)上的Python 3.6上占用的内存超过8MB:

>>> import sys
>>> sys.getsizeof(y)
8697464
>>> sys.getsizeof(y) / 2 ** 20   # Mb
8.294548034667969


空的list对象占用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


在Python 3.6上,我也不能重复本文的声明,部分是因为Python看到了很多内存管理方面的改进,部分是因为本文在某些方面是错误的。

copy.deepcopy()关于列表和整数的行为在copy.deepcopy()的悠久历史中从未改变(请参阅first revision of the module, added in 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


发生的事情是Python的内存管理系统正在分配新的内存块以进行其他扩展。这并不是说新的y列表对象会占用将近93MiB的内存,而仅仅是OS分配给Python进程的额外内存,当该进程为对象堆请求更多的内存时。列表对象本身要小得多。

Python 3 tracemalloc module关于实际发生的事情更加准确:

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


Python 3.x内存管理器和列表实现比2.7中的智能。显然,新的列表对象能够放入创建x时已预先分配的现有内存中。

我们可以使用manually built Python 2.7.12 tracemalloc binarysmall patch to 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


我注意到作者也很困惑:


  copy.deepcopy复制两个列表,然后再次分配〜50 MB(我不确定50 MB-31 MB = 19 MB的额外开销来自何处)


(加粗强调我的)。

这里的错误是假定Python进程大小中的所有内存更改都可以直接归因于特定对象,但是实际情况要复杂得多,因为内存管理器可以添加(和删除!)内存“ arenas”(内存块)保留为堆使用,如果需要的话,将在更大的块中这样做。这里的过程很复杂,因为它取决于interactions between Python's manager and the OS malloc implementation details。作者发现了有关Python模型的较旧文章,他们误解为最新的author of that article themselves has already tried to point this out。从Python 2.5开始,关于Python不释放内存的说法不再成立。

令人不安的是,同样的误解导致作者建议不要使用pickle,但实际上,即使在Python 2上,该模块也很少添加簿记内存来跟踪递归结构。参见this gist for my testing methodology;在Python 2.7上使用cPickle将增加46MiB一次(将create_file()调用加倍将不会进一步增加内存)。在Python 3中,内存更改完全消失了。

我将与Theano团队就该帖子打开一个对话框,该文章是错误的,令人困惑的,而且Python 2.7很快就会被完全淘汰,因此他们确实应该专注于Python 3的内存模型。 (*)

当您从range()创建新列表而不是副本时,您会看到与第一次创建x类似的内存增加,因为除了创建新列表之外,还将创建一组新的整数对象列出对象。除了a specific set of small integers之外,Python不会对range()操作进行缓存和重复使用整数值。



(*)附录:我在Thano项目中打开了issue #6619。该项目与我的评估和removed the page from their documentation一致,尽管他们尚未更新发布的版本。

关于python - Python:无法复制内存使用情况的测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53543290/

相关文章:

linux - 锁频 Cortex A15 设备上内存带宽(但不是 CPU 性能)的奇怪小变化

visual-studio - Windows XP 上的 Visual Studio

java - 一个java程序的总分配

python - 有没有办法通过 python 3 将任务添加到 Windows 任务调度程序?

python - 获取keras模型的学习率

python - 枕头图像类型错误: an integer is required (got type tuple)

python - matplotlib 不显示 x 轴和 y 轴标签和值以及图表标题

c - 指针与堆中内存的关系

java - 集合中的内存消耗

Java Unsafe.copyMemory java.lang.IllegalArgumentException 异常