tuple
在Python中占用更少的内存空间:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
而list
需要更多的内存空间:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Python内存管理内部会发生什么?
答案 0 :(得分:129)
我假设你正在使用CPython和64位(我在CPython 2.7 64位上获得了相同的结果)。其他Python实现可能存在差异,或者如果你有32位Python。
无论实现如何,list
都是可变大小的,而tuple
是固定大小的。
因此tuple
可以直接在结构中存储元素,另一方面,列表需要一个间接层(它存储指向元素的指针)。这个间接层是一个指针,在64位系统上是64位,因此是8字节。
但list
还有另外一件事:他们过度分配。否则list.append
将是O(n)
操作始终 - 使其分摊O(1)
(更快!!!)它过度分配。但现在它必须跟踪已分配的大小和已填充大小(tuple
只需要存储一个大小,因为已分配和填充的大小始终相同)。这意味着每个列表必须存储另一个“大小”,在64位系统上是64位整数,再为8个字节。
因此list
需要比tuple
至少多16个字节的内存。为什么我说“至少”?因为过度分配。过度分配意味着它分配的空间超出了需要。但是,过度分配的数量取决于您创建列表的“方式”以及追加/删除历史记录:
>>> l = [1,2,3]
>>> l.__sizeof__()
64
>>> l.append(4) # triggers re-allocation (with over-allocation), because the original list is full
>>> l.__sizeof__()
96
>>> l = []
>>> l.__sizeof__()
40
>>> l.append(1) # re-allocation with over-allocation
>>> l.__sizeof__()
72
>>> l.append(2) # no re-alloc
>>> l.append(3) # no re-alloc
>>> l.__sizeof__()
72
>>> l.append(4) # still has room, so no over-allocation needed (yet)
>>> l.__sizeof__()
72
我决定创建一些图像以配合上面的解释。也许这些都很有用
这是(示意图)在您的示例中存储在内存中的方式。我强调了红色(自由手)循环的差异:
这实际上只是一个近似值,因为int
对象也是Python对象,CPython甚至重用小整数,因此内存中对象的可能更精确的表示(尽管不是可读的)将是:
有用的链接:
tuple
struct in CPython repository for Python 2.7 list
struct in CPython repository for Python 2.7 int
struct in CPython repository for Python 2.7 请注意,__sizeof__
并未真正返回“正确”大小!它只返回存储值的大小。但是,当您使用sys.getsizeof
时,结果会有所不同:
>>> import sys
>>> l = [1,2,3]
>>> t = (1, 2, 3)
>>> sys.getsizeof(l)
88
>>> sys.getsizeof(t)
72
有24个“额外”字节。这些是真实,这是__sizeof__
方法中未考虑的垃圾收集器开销。这是因为你通常不应该直接使用魔术方法 - 使用知道如何处理它们的函数,在这种情况下:sys.getsizeof
(实际上adds the GC overhead到__sizeof__
返回的值})。
答案 1 :(得分:30)
我将深入研究CPython代码库,以便我们可以看到实际计算的大小。 在您的具体示例中,没有执行过度分配,因此我无法触及。
我将在这里使用64位值。
list
的大小由以下函数list_sizeof
计算:
static PyObject *
list_sizeof(PyListObject *self)
{
Py_ssize_t res;
res = _PyObject_SIZE(Py_TYPE(self)) + self->allocated * sizeof(void*);
return PyInt_FromSsize_t(res);
}
此处Py_TYPE(self)
是抓取ob_type
self
(返回PyList_Type
)的宏,而_PyObject_SIZE
是另一个抓取tp_basicsize
的宏从那种类型。 tp_basicsize
计算为sizeof(PyListObject)
,其中PyListObject
是实例结构。
PyListObject
structure有三个字段:
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
这些评论(我修剪过)解释了它们是什么,请按照上面的链接阅读它们。 PyObject_VAR_HEAD
扩展为三个8字节字段(ob_refcount
,ob_type
和ob_size
),因此24
字节贡献。
所以现在res
是:
sizeof(PyListObject) + self->allocated * sizeof(void*)
或:
40 + self->allocated * sizeof(void*)
如果列表实例包含已分配的元素。第二部分计算他们的贡献。 self->allocated
,顾名思义,保存已分配元素的数量。
没有任何元素,列表的大小计算为:
>>> [].__sizeof__()
40
,即实例struct的大小。
tuple
个对象无法定义tuple_sizeof
功能。相反,他们使用object_sizeof
来计算其大小:
static PyObject *
object_sizeof(PyObject *self, PyObject *args)
{
Py_ssize_t res, isize;
res = 0;
isize = self->ob_type->tp_itemsize;
if (isize > 0)
res = Py_SIZE(self) * isize;
res += self->ob_type->tp_basicsize;
return PyInt_FromSsize_t(res);
}
对于list
s,它抓取tp_basicsize
,如果对象具有非零tp_itemsize
(意味着它具有可变长度实例),则将其乘以数字元组中的项目(通过Py_SIZE
获得)与tp_itemsize
。
tp_basicsize
再次使用sizeof(PyTupleObject)
:
PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
Py_SIZE
因此,没有任何元素(即0
返回sizeof(PyTupleObject)
),空元组的大小等于>>> ().__sizeof__()
24
:
tp_basicsize
是吗?好吧,这里有一个奇怪的地方,我还没有找到解释,tuple
sizeof(PyTupleObject) - sizeof(PyObject *)
的实际计算方法如下:
8
为什么从tp_basicsize
中删除了额外的list
个字节,这是我无法找到的。 (有关可能的解释,请参阅MSeifert的评论)
但是,这基本上与您的具体示例中的不同。 bind
还保留了许多已分配的元素,这有助于确定何时再次进行过度分配。
现在,当添加其他元素时,列表确实会执行此过度分配以实现O(1)追加。这导致更大的尺寸,因为MSeifert在他的回答中很好地涵盖了。
答案 2 :(得分:29)
MSeifert答案涵盖范围广泛;为了保持简单,你可以想到:
tuple
是不可变的。设置后,您无法更改它。所以你事先知道需要为该对象分配多少内存。
list
是可变的。您可以在其中添加或删除项目。它必须知道它的大小(对于内部impl。)。它根据需要调整大小。
没有免费的饭菜 - 这些功能需要付出代价。因此列表的内存开销。
答案 3 :(得分:3)
元组的大小是前缀的,这意味着在元组初始化时,解释器为所包含的数据分配足够的空间,这就是它的结束,使它不可变(不可能)虽然列表是一个可变对象,因此意味着动态分配内存,因此为了避免每次附加或修改列表时分配空间(分配足够的空间来包含已更改的数据并将数据复制到其中),它会分配额外的未来追加,修改的空间......几乎总结了它。