奇怪的 Python 内存分配
Strange Python memory allocation
在弄清楚 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 行增加。
在第 7 行中,这可能是探查器的某种形式的开销吗?从我使用 sys.getsizeof()
可以看出,数组在每次附加时增加 8 个字节,没有突然跳跃。
起初我认为这可能与 Python 列表的情况类似,在这种情况下,每 4 个追加到 32 字节块中就会分配一次内存,但事实似乎并非如此。
没有函数或分析器,我看不到与您在 post 中显示的类似的行为。我能看到的唯一奇怪之处是 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
创建 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
不会 return(到 OS)每个空闲块。
然后将新数组分配给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
都起作用。我们大多数人都不知道所有的细节,所以我们只能对正在发生的事情做出粗略的猜测。
在弄清楚 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 行增加。
在第 7 行中,这可能是探查器的某种形式的开销吗?从我使用 sys.getsizeof()
可以看出,数组在每次附加时增加 8 个字节,没有突然跳跃。
起初我认为这可能与 Python 列表的情况类似,在这种情况下,每 4 个追加到 32 字节块中就会分配一次内存,但事实似乎并非如此。
没有函数或分析器,我看不到与您在 post 中显示的类似的行为。我能看到的唯一奇怪之处是 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
创建 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
不会 return(到 OS)每个空闲块。
然后将新数组分配给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
都起作用。我们大多数人都不知道所有的细节,所以我们只能对正在发生的事情做出粗略的猜测。