Python中Bytearray和List的区别

8

我很想知道Python中Bytearray和List之间的内存管理有什么不同。

我找到了一些类似Bytearray和List之间的区别的问题,但并没有确切回答我的问题。

我的问题是...

from array import array
>>> x = array("B", (1,2,3,4))
>>> x.__sizeof__()
36
>>> y = bytearray((1,2,3,4))
>>> y.__sizeof__()
32
>>> z = [1,2,3,4]
>>> z.__sizeof__()
36

我们可以看到,在列表/数组和字节数组之间存在大小差异(4个元素的列表/数组占用36字节,而4个元素的字节数组占用32字节)。有人能解释一下这是为什么吗?对于字节数组来说,它占用了32字节的内存,因为4个元素(4*8==32),这是有道理的,但是如何解释列表和array.array呢?

# Lets take the case of bytearray ( which makes more sense to me at least :p)
for i in y:
        print(i, ": ", id(i))

1 :  499962320
2 :  499962336 #diff is 16 units
3 :  499962352 #diff is 16 units
4 :  499962368 #diff is 16 units

当每个元素仅占用8字节时,为何相邻两个元素之间的差异为16个单位?这是否意味着每个内存地址指针都指向半个字节(nibble)?

此外,分配整数的内存的标准是什么?我读到Python会根据整数的值分配更多的内存(如果我错了请纠正),例如数字越大分配的内存越多。

例如:

>>> y = 10
>>> y.__sizeof__()
14
>>> y = 1000000
>>> y.__sizeof__()
16
>>> y = 10000000000000
>>> y.__sizeof__()
18

Python分配内存的标准是什么?为什么Python占用的内存要比C多得多(我的机器是64位),而它们都只处于整数范围内(2**64)?
元数据:
Python版本: '3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06) [MSC v.1600 32 bit (Intel)]'
机器架构: 64位
附言:请指引我一篇好的文章,以更好地解释Python的内存管理。我已经花了将近一个小时来弄清楚这些事情,最终在Stack Overflow上提出了这个问题 :(

2
好问题,已点赞。嘿,你很幸运:在我的Linux Xubuntu 64位机器上,CPython 3.4.3 y.__sizeof__() 给出了 28 ,对于 y=10y=1M 也是一样的结果,而 y=10000000000000 则是 32 - Pynchia
嗨@Pynchia,我的Python是32位的,尽管我的机器是64位的。我不确定,但可能是这个原因。让我们等待有人澄清一下。 - Sravan K Ghantasala
1个回答

3
我不断言这是一个完整的答案,但以下是一些了解它的提示。
bytearray 是字节序列,list 是对象引用序列。因此 [1, 2, 3] 实际上保存了指向其他地方存储在内存中的整数变量的内存指针。为了计算列表结构的总内存消耗,我们可以使用以下方法(我在所有进一步的操作中都使用 sys.getsizeof,它调用 __sizeof__ 加上 GC 开销)。
>>> x = [1,2,3]
>>> sum(map(getsizeof, x)) + getsizeof(x)
172

不同的机器可能会产生不同的结果。

此外,请看这个:

>> getsizeof([])
64

这是因为列表是可变的。为了提高速度,该结构分配一些内存范围以存储对象的引用(加上一些元数据的存储,例如列表的长度)。当你添加项目时,下一个内存单元会填充对这些项目的引用。当没有足够的空间来存储新项目时,将分配新的、更大的范围,将现有数据复制到那里并释放旧的范围。这称为动态数组。

您可以通过运行此代码观察此行为。

import sys 
data=[]
n=15
for k in range(n):
    a = len(data)
    b = sys.getsizeof(data)
    print('Length: {0:3d}; Size in bytes: {1:4d}'.format(a, b))
    data.append(None)

我的结果:

Length:   0; Size in bytes:   64 
Length:   1; Size in bytes:   96
Length:   2; Size in bytes:   96 
Length:   3; Size in bytes:   96
Length:   4; Size in bytes:   96 
Length:   5; Size in bytes:  128
Length:   6; Size in bytes:  128 
Length:   7; Size in bytes:  128
Length:   8; Size in bytes:  128 
Length:   9; Size in bytes:  192
Length:  10; Size in bytes:  192 
Length:  11; Size in bytes:  192
Length:  12; Size in bytes:  192 
Length:  13; Size in bytes:  192
Length:  14; Size in bytes:  192

我们可以看到,有64个字节被用来存储8个内存地址(每个地址为64位)。
对于bytearray()也几乎一样(将第二行改为data = bytearray()并在最后一个中添加1)。
Length:   0; Size in bytes:   56
Length:   1; Size in bytes:   58
Length:   2; Size in bytes:   61
Length:   3; Size in bytes:   61
Length:   4; Size in bytes:   63
Length:   5; Size in bytes:   63
Length:   6; Size in bytes:   65
Length:   7; Size in bytes:   65
Length:   8; Size in bytes:   68
Length:   9; Size in bytes:   68
Length:  10; Size in bytes:   68
Length:  11; Size in bytes:   74
Length:  12; Size in bytes:   74
Length:  13; Size in bytes:   74
Length:  14; Size in bytes:   74

不同之处在于,内存现在用于保存实际的字节值,而不是指针。

希望这能帮助你进一步研究。


嗨@anti1869,感谢您的评论。它非常详尽和有用。但是,在您的评论中我有以下问题。我无法在此处添加所有信息,因此在下面添加另一个评论。谢谢。 - Sravan K Ghantasala
根据您的解释,我们已经理解了列表的情况,但是为什么字节数组的大小从56开始?并且为什么在达到74后它保持稳定?如果您能提供更多关于为什么初始大小为64和56的信息,我们将不胜感激。谢谢。 - Sravan K Ghantasala
1
看一下那个数据结构的源代码。在那里,您将看到内部容器结构以及初始化时分配了什么内存。此外,增长算法非常清晰可见。https://github.com/python/cpython/blob/master/Objects/listobject.c - anti1869

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