为什么元组的内存空间less于列表?
一个tuple
在Python中占用较less的内存空间:
>>> a = (1,2,3) >>> a.__sizeof__() 48
而list
s需要更多的内存空间:
>>> b = [1,2,3] >>> b.__sizeof__() 64
Python内存pipe理的内部会发生什么?
我假设你正在使用CPython和64位(我在CPython 2.7 64位上得到了相同的结果)。 在其他Python实现中可能有区别,或者如果你有一个32位的Python。
无论实现如何, list
s是可变大小的,而tuple
s是固定大小的。
所以tuple
s可以直接在结构中存储元素,列表另一方面需要一个间接层(它存储一个指向元素的指针)。 这个间接层是一个指针,在64位系统上是64位的,因此是8个字节。
但是还有另外一个list
做:他们超额分配。 否则, list.append
总是一个O(n)
操作 – 使其分摊O(1)
(快得多!!!)。 但是现在必须跟踪分配的大小和填充的大小( tuple
只需要存储一个大小,因为分配和填充的大小总是相同的)。 这意味着每个列表必须存储另一个“大小”,在64位系统上是64位整数,也是8个字节。
所以list
需要比tuple
s多至less16个字节的内存。 为什么我说“至less”? 由于过度分配。 超额分配意味着分配的空间超过需求。 但是,超额分配的数量取决于您如何创build列表和附加/删除历史logging:
>>> 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
图片
我决定创build一些图片来陪同上面的解释。 也许这些是有帮助的
这是(示意性)在你的例子中存储在内存中的方式。 我强调了红色(徒手)循环的不同之处:
这实际上只是一个近似值,因为int
对象也是Python对象,CPython甚至会重用小整数,因此内存中的对象可能更准确地表示(尽pipe不可读):
有用的链接:
- Python 2.7的CPython存储库中的
tuple
结构 - 在Python 2.7的CPython存储库中
list
结构体 - 在Python 2.7的CPython存储库中的
int
结构
请注意, __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
(实际上将GC开销添加到从__sizeof__
返回的值)。
我将深入探讨CPython的代码库,以便我们看到如何计算大小。 在你的具体例子中 , 没有执行超额分配,所以我不会涉及到这一点 。
我将在这里使用64位值,就像你一样。
list
s的大小是从以下函数list_sizeof
计算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)
是一个macros,它ob_type
self
的ob_type
(返回PyList_Type
),而_PyObject_SIZE
是另一个从该typestp_basicsize
macros。 tp_basicsize
计算为sizeof(PyListObject)
,其中PyListObject
是实例结构。
PyListObject
结构有三个字段:
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
数量(通过Py_SIZE
获取)与tp_itemsize
。
tp_basicsize
再次使用PyTupleObject
结构包含的 sizeof(PyTupleObject)
:
PyObject_VAR_HEAD # 24 bytes PyObject *ob_item[1]; # 8 bytes
因此,没有任何元素(即Py_SIZE
返回0
),空元组的大小等于sizeof(PyTupleObject)
:
>>> ().__sizeof__() 24
是吧? 那么,这里有一个古怪的地方,我没有find一个解释, tuple
s的tp_basicsize
实际上是按如下计算的:
sizeof(PyTupleObject) - sizeof(PyObject *)
为什么从tp_basicsize
删除额外的8
个字节是我一直无法find的东西。 (见MSeifert对可能的解释的评论)
但是,这在你的具体例子中基本上是不同的。 list
也围绕着一些分配的元素,这有助于确定何时再次分配。
现在,当添加其他元素时,列表确实会执行这种过度分配以实现O(1)附加。 这导致更大的尺寸,因为MSeifert的答案很好。
MSeifert答案涵盖广泛; 保持简单你可以想到:
tuple
是不可变的。 一旦设置,你不能改变它。 因此,您事先知道需要为该对象分配多less内存。
list
是可变的。 您可以添加或删除项目。 它必须知道它的大小(对于内部impl)。 它根据需要resize。
有没有免费的餐点 – 这些function与成本。 因此列表的内存开销。
元组的大小是前缀,这意味着在元组初始化时,解释器为包含的数据分配足够的空间,这是它的结束,它是不可变的(不能被修改),而列表是一个可变的对象,因此意味着dynamic分配内存,所以为了避免在每次添加或修改列表时分配空间(分配足够的空间来容纳已更改的数据并将数据复制到该列表中),它会为将来的追加,修改…分配额外空间总结。