【问题标题】:Strange Python memory allocation奇怪的 Python 内存分配
【发布时间】:2020-07-23 23:48:24
【问题描述】:

在试图弄清楚 Python 的垃圾收集系统是如何工作的时,我偶然发现了这个奇怪的东西。运行这个简单的代码:

import numpy as np
from memory_profiler import profile

@profile
def my_func():
    a = np.random.rand(1000000)
    a = np.append(a, [1])
    a = np.append(a, [2])
    a = np.append(a, [3])
    a = np.append(a, [4])
    a = np.append(a, [5])
    b = np.append(a, [6])
    c = np.append(a, [7])
    d = np.append(a, a)

    return a

if __name__ == '__main__':
    my_func()

在我的 MacBook 上使用 memory_profiler 版本 0.52 和 Python 3.7.6,我得到以下输出:

Line #    Mem usage    Increment   Line Contents
================================================
     4     54.2 MiB     54.2 MiB   @profile
     5                             def my_func():
     6     61.8 MiB      7.7 MiB       a = np.random.rand(1000000)
     7     69.4 MiB      7.6 MiB       a = np.append(a, [1])
     8     69.4 MiB      0.0 MiB       a = np.append(a, [2])
     9     69.4 MiB      0.0 MiB       a = np.append(a, [3])
    10     69.4 MiB      0.0 MiB       a = np.append(a, [4])
    11     69.4 MiB      0.0 MiB       a = np.append(a, [5])
    12     69.4 MiB      0.0 MiB       b = np.append(a, [6])
    13     77.1 MiB      7.6 MiB       c = np.append(a, [7])
    14     92.3 MiB     15.3 MiB       d = np.append(a, a)
    15                             
    16     92.3 MiB      0.0 MiB       return a

有两件事很奇怪。首先,为什么第 7 行的内存增加比第 8-11 行更明显?其次,为什么第 12 行没有像第 13 行一样增加内存?

请注意,如果我删除第 12-14 行,我仍然会在第 7 行中获得内存的增加。所以这不是第 12 行内存实际增加的错误,而是 memory_profiler 错误地显示第 7 行的增加。

【问题讨论】:

    标签: python numpy memory-profiling


    【解决方案1】:

    创建 a 会生成一个 8e6 字节的数组(检查 `a.nybtes)

     6     61.8 MiB      7.7 MiB       a = np.random.rand(1000000)
    

    np.append 创建一个新数组(它是concatenate,而不是列表追加),所以我们又增加了 8MB。

     7     69.4 MiB      7.6 MiB       a = np.append(a, [1])
    

    我的猜测是,在以下步骤中,它使用这两个 8MB 块来回循环。 numpy 不会返回(给操作系统)每个空闲块。

    然后将新数组分配给c。 a 和 b 仍然存在。 (我第一次看到这个时错过了b。)

    13     77.1 MiB      7.6 MiB       c = np.append(a, [7])
    

    d 的大小是a 的两倍,因此占了 15MB 的跳跃。 a,b,c 仍然存在。

    14     92.3 MiB     15.3 MiB       d = np.append(a, a)
    

    b 和 c 只是比 a 大一两个数字 - 所以每个数字大约占用 8MB。这似乎说明了一切!

    在跟踪内存使用时,请记住 numpy、python 和 OS 都起作用。我们大多数人并不知道所有细节,所以我们只能粗略猜测发生了什么。

    【讨论】:

      【解决方案2】:

      在第 7 行中,这可能是分析器的某种形式的开销吗?从我可以看到使用sys.getsizeof() 时,数组在每次追加时增加了 8 个字节,没有突然跳转。

      起初我认为这可能与 Python 列表的情况类似,其中每 4 个附加到 32 字节块中分配一次内存,但情况似乎并非如此。

      如果没有函数或分析器,我看不到与您在帖子中显示的行为类似的行为。我能看到的唯一奇怪之处是d 的大小并不完全是a 的两倍。

      import numpy as np
      import sys
      
      a = np.random.rand(1000000)
      
      sys.getsizeof(a)
      Out[55]: 8000096
      
      a = np.append(a, [1])
      
      sys.getsizeof(a)
      Out[57]: 8000104
      
      a = np.append(a, [2])
      
      sys.getsizeof(a)
      Out[59]: 8000112
      
      a = np.append(a, [3])
      
      sys.getsizeof(a)
      Out[61]: 8000120
      
      a = np.append(a, [4])
      
      sys.getsizeof(a)
      Out[63]: 8000128
      
      a = np.append(a, [5])
      
      sys.getsizeof(a)
      Out[66]: 8000136
      
      a = np.append(a, [6])
      
      sys.getsizeof(a)
      Out[68]: 8000144
      
      a = np.append(a, [7])
      
      sys.getsizeof(a)
      Out[71]: 8000152
      
      d = np.append(a, a)
      
      sys.getsizeof(d)
      Out[73]: 16000208
      

      【讨论】:

        猜你喜欢
        • 2012-07-01
        • 2018-05-26
        • 1970-01-01
        • 2021-05-19
        • 2014-02-15
        • 2018-08-03
        • 1970-01-01
        • 2014-07-28
        • 2016-12-03
        相关资源
        最近更新 更多