Python中列表的内存大小

4
我正在尝试使用列表进行一些实验,并遇到了自引用列表。我在 SO 上搜索并得到了一些关于它的基本问题的答案。但是当我尝试获取不同长度的自引用列表的内存大小时,我发现了一个有趣的模式。
重现代码:
import sys

memory_size = {}

for length in range(50):
    lst = []
    for length_loop in range(length):
        lst.append(lst)
    memory_size[length] = sys.getsizeof(lst)

memory_size的值:

{0: 64, 1: 96, 2: 96, 3: 96, 4: 96, 5: 128, 6: 128, 7: 128, 8: 128, 9: 192, 10: 192, 11: 192, 12: 192, 13: 192, 14: 192, 15: 192, 16: 192, 17: 264, 18: 264, 19: 264, 20: 264, 21: 264, 22: 264, 23: 264, 24: 264, 25: 264, 26: 344, 27: 344, 28: 344, 29: 344, 30: 344, 31: 344, 32: 344, 33: 344, 34: 344, 35: 344, 36: 432, 37: 432, 38: 432, 39: 432, 40: 432, 41: 432, 42: 432, 43: 432, 44: 432, 45: 432, 46: 432, 47: 528, 48: 528, 49: 528}

在绘制上述数据点时

enter image description here

Python 3.7.3 (default, Mar 27 2019, 16:54:48)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.5.0 -- An enhanced Interactive Python. Type '?' for help.

为什么自引用列表的内存大小在一定长度范围内保持不变,而在某个长度后增加?此外,内存大小的增加也是不同的。

可能是重复问题:https://dev59.com/w2865IYBdhLWcg3wOcDy - skullgoblet1089
3
这与自我引用列表无关,如果添加“1”,您会观察到相同的情况。 - Thierry Lathuille
2个回答

7

如果使用append方法构建列表,它们总是遵循这种模式。

需要理解的一个关键点是,sys.getsizeof 不包括列表中引用的对象,只计算列表对象本身的大小。现在,Python list 对象在底层实现为数组列表,因此基本上有一个 PyObject 头部(例如,16字节的开销),然后是一个 PyObject 指针的原始数组(这就是为什么它们可以是异构的,并且引用自身)。

这个底层数组是被过度分配的,并且以保证摊销的常量时间.append操作来重新调整大小。

换句话说,Python list 对象有摊销常量时间.append,因此像for x in range(N): my_list.append(0)这样的操作是一个线性时间操作,因为底层缓冲区在每次迭代时不会被重新分配。

事实上,任何对象都符合这一模式,比如 None

In [24]: import sys
    ...:
    ...: memory_size = {}
    ...:
    ...: for length in range(50):
    ...:     lst = []
    ...:     for length_loop in range(length):
    ...:         lst.append(None)
    ...:     memory_size[length] = sys.getsizeof(lst)
    ...:

In [25]: memory_size
Out[25]:
{0: 72,
 1: 104,
 2: 104,
 3: 104,
 4: 104,
 5: 136,
 6: 136,
 7: 136,
 8: 136,
 9: 200,
 10: 200,
 11: 200,
 12: 200,
 13: 200,
 14: 200,
 15: 200,
 16: 200,
 17: 272,
 18: 272,
 19: 272,
 20: 272,
 21: 272,
 22: 272,
 23: 272,
 24: 272,
 25: 272,
 26: 352,
 27: 352,
 28: 352,
 29: 352,
 30: 352,
 31: 352,
 32: 352,
 33: 352,
 34: 352,
 35: 352,
 36: 440,
 37: 440,
 38: 440,
 39: 440,
 40: 440,
 41: 440,
 42: 440,
 43: 440,
 44: 440,
 45: 440,
 46: 440,
 47: 536,
 48: 536,
 49: 536}

为了使您信服,这里提供了自我引用列表:

In [26]: import sys
    ...:
    ...: memory_size = {}
    ...:
    ...: for length in range(50):
    ...:     lst = []
    ...:     for length_loop in range(length):
    ...:         lst.append(lst)
    ...:     memory_size[length] = sys.getsizeof(lst)
    ...:

In [27]: memory_size
Out[27]:
{0: 72,
 1: 104,
 2: 104,
 3: 104,
 4: 104,
 5: 136,
 6: 136,
 7: 136,
 8: 136,
 9: 200,
 10: 200,
 11: 200,
 12: 200,
 13: 200,
 14: 200,
 15: 200,
 16: 200,
 17: 272,
 18: 272,
 19: 272,
 20: 272,
 21: 272,
 22: 272,
 23: 272,
 24: 272,
 25: 272,
 26: 352,
 27: 352,
 28: 352,
 29: 352,
 30: 352,
 31: 352,
 32: 352,
 33: 352,
 34: 352,
 35: 352,
 36: 440,
 37: 440,
 38: 440,
 39: 440,
 40: 440,
 41: 440,
 42: 440,
 43: 440,
 44: 440,
 45: 440,
 46: 440,
 47: 536,
 48: 536,
 49: 536}

个体大小的差异归结于诸如Python版本和系统架构之类的问题(例如,在32位系统上,指针为4个字节而不是8个字节,并且不同版本的Python可以自由更改实现细节,例如空列表的大小)。注意,以上内容是在Python 3.7上运行的,如果我使用另一个环境:
(base) juanarrivillaga@173-11-109-137-SFBA ~ % python -c "import sys; print(f'{sys.version}\nEmpty List Size: {sys.getsizeof([])}')"
3.8.1 (default, Jan  8 2020, 16:15:59)
[Clang 4.0.1 (tags/RELEASE_401/final)]
Empty List Size: 56

你的情况下为什么初始化大小是72 - bigbounty
@bigbounty 这取决于你的 Python 版本,甚至是你的系统架构/操作系统。上述代码在 3.7.7 上生成的大小为 56 字节,在 3.8 上则为 56 字节。 - juanpa.arrivillaga
明白了。谢谢。 - bigbounty

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接