代码之家  ›  专栏  ›  技术社区  ›  tryingtosolve

python:无法复制内存使用测试

  •  4
  • tryingtosolve  · 技术社区  · 6 年前

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

    基本上,post声明给出了以下代码片段:

    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
    

    这篇文章可能已经过时了——profiler包或python可能已经改变了。无论如何,我的问题是,在Python3.6.x中

    (1)应该 copy.deepcopy(x) (如上面代码中所定义的)消耗非常多的内存?

    (2)为什么我不能复制?

    (3)如果我重复 x = list(range(1000000)) 之后 del x 内存会增加和我最初分配的一样吗? X=列表(范围(1000000)) (如我的代码第5行所示)?

    1 回复  |  直到 6 年前
        1
  •  5
  •   Martijn Pieters    6 年前

    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万个引用创建一个新的列表对象,该对象在MyOSX 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
    

    在Python3.6上,我也不能复制文章中的声明,部分原因是Python已经看到了很多内存管理方面的改进,部分原因是文章在几个方面是错误的。

    行为 复制.deepcopy() 关于列表和整数 从未 复制.deepcopy() (见 first revision of the module, added in 1995 ,即使在Python2.7上,对内存图的解释也是错误的。

    具体来说,我 可以 使用Python2.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的内存管理系统正在分配一个新的内存块来进行额外的扩展。不是新的 是的 list object占用了将近93mib的内存,这只是操作系统在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 binary 以及 small patch to memory_profile.py 是的。现在我们在Python2.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进程大小中的所有内存更改都可以直接归因于特定对象,但实际情况要复杂得多,因为内存管理器可以添加。 然后移开! 内存“ARNEAS”,根据需要为堆保留的内存块,如果有意义的话,将在更大的块中这样做。这里的过程是复杂的,这取决于 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 ;使用 cPickle 在Python2.7上,增加了一次46MIB(将 create_file() 调用不会导致内存进一步增加)。在python 3中,内存更改已经完全消失了。

    我将打开TeaNo团队关于这个帖子的对话,这篇文章是错误的,令人困惑的,Python 2.7很快就会完全过时,所以他们真的应该关注Python 3的内存模型。 (*)

    当您创建 新列表 范围() 不是复制,你会看到内存的增加和创建一样。 第一次,因为除了新的list对象之外,还要创建一组新的整数对象。除了 a specific set of small integers ,python不缓存和重用 范围() 操作。


    (*) 附录 :我打开了 issue #6619 和塔诺计划。这个项目符合我的评估,而且 removed the page from their documentation ,尽管他们还没有更新发布的版本。