Python 内存中列表的大小
我刚刚在内存中试验了python数据结构的大小。我写了以下片段:Python 内存中列表的大小,python,python-3.x,Python,Python 3.x,我刚刚在内存中试验了python数据结构的大小。我写了以下片段: import sys lst1=[] lst1.append(1) lst2=[1] print(sys.getsizeof(lst1), sys.getsizeof(lst2)) 我在以下配置上测试了代码: Windows 7 64位,Python3.1:输出为:5240,因此lst1有52个字节,lst2有40个字节 使用Python3.2的Ubuntu 11.4 32位:输出为48 32 Ubuntu 11.4 32位P
import sys
lst1=[]
lst1.append(1)
lst2=[1]
print(sys.getsizeof(lst1), sys.getsizeof(lst2))
我在以下配置上测试了代码:
- Windows 7 64位,Python3.1:输出为:
,因此lst1有52个字节,lst2有40个字节5240
- 使用Python3.2的Ubuntu 11.4 32位:输出为
48 32
- Ubuntu 11.4 32位Python 2.7:
48 36
在getsizeof函数的python文档中,我发现了以下内容:
…如果对象由垃圾收集器管理,则会增加垃圾收集器开销。
在我的小示例中是否会出现这种情况?对不起,前面的评论有点简短
现在发生的事情是,您正在查看列表是如何分配的(我想您可能只是想看看事情有多大——在这种情况下,请使用sys.getsizeof()
)
在列表中添加内容时,可能会发生以下两种情况之一:
[]
或[1]
(或两者)都是特殊情况,只分配了足够的内存(在这些常见情况下可以节省内存),然后追加上面描述的“抓取新块”会增加更多内存,我也不会感到惊讶
但我不知道确切的细节——这就是动态数组通常的工作方式。python中列表的精确实现将被精细地调整,以使其适合典型的python程序。所以我真正想说的是,你不能相信一个列表的大小来告诉你它到底包含了多少——它可能包含额外的空间,而额外的可用空间的数量很难判断或预测
ps一个简洁的替代方法是将列表作为(值,指针)
对,其中每个指针指向下一个元组。通过这种方式,您可以递增地增加列表,尽管使用的总内存更高。这是一个链表(python使用的更像是向量或动态数组)
[更新]见Eli的精彩答案。他/她解释说,
[]
和[1]
都是精确分配的,但是添加到[]
后会分配额外的块。代码中的注释就是我上面所说的(这称为“过度分配”,金额与我们拥有的部分成比例,因此平均(“摊销”)成本与大小成比例)。这里有一个更完整的交互式会话,它将帮助我解释发生了什么(Windows XP 32位上的Python 2.6,但实际上并不重要):
请注意,空列表比其中包含[1]
的列表小一些。然而,当添加元素时,它会变得更大
原因是CPython源代码中Objects/listobject.c
中的实现细节
空列表
创建空列表时,[]
不会为元素分配空间-这可以在PyList\u New
中看到。36字节是32位计算机上列表数据结构本身所需的空间量
包含一个元素的列表
创建包含单个元素的列表时,除了列表数据结构本身所需的内存外,还会为一个元素分配空间。同样,这可以在PyList\u New
中找到。给定size
作为参数,它计算:
nbytes = size * sizeof(PyObject *);
然后是:
if (size <= 0)
op->ob_item = NULL;
else {
op->ob_item = (PyObject **) PyMem_MALLOC(nbytes);
if (op->ob_item == NULL) {
Py_DECREF(op);
return PyErr_NoMemory();
}
memset(op->ob_item, 0, nbytes);
}
Py_SIZE(op) = size;
op->allocated = size;
让我们做些数学题
让我们看看我在文章开头的会话中引用的数字是如何达到的
因此36字节是列表数据结构本身在32位上所需的大小。对于单个元素,为一个指针分配了空间,因此额外增加了4个字节—总共40个字节。好的,到目前为止
当对空列表调用app1
时,它将使用size=1
调用list\u resize
。根据list\u resize
的过度分配算法,1之后的下一个最大可用大小是4,因此将为4个指针分配位置。4*4=16字节,36+16=52
事实上,一切都是有道理的:-)下面是列表增长模式的快速演示。更改range()中的第三个参数将更改输出,使其看起来不像listobject.c中的注释,但仅添加一个元素时的结果似乎非常准确
allocated = 0
for newsize in range(0,100,1):
if (allocated < newsize):
new_allocated = (newsize >> 3) + (3 if newsize < 9 else 6)
allocated = newsize + new_allocated;
print newsize, allocated
allocated=0
对于范围(0100,1)内的新闻大小:
如果(已分配<新闻大小):
新分配=(新闻大小>>3)+(如果新闻大小<9,则为3,否则为6)
已分配=新闻大小+已分配的新内容;
打印新闻大小,分配
公式根据系统架构进行更改
(尺寸-36)/4,适用于32位机器和
(大小为64)/8,适用于64位计算机
36,64-基于机器的空列表的大小
4,8-基于机器的列表中单个元素的大小列表不是以增量方式分配的,而是以“块”的形式分配的(并且块随着列表变大而变大)。这是必要的,以便附加数据的摊销成本较低。因此,我猜分配器在这两种情况下的工作方式不同。但事实上,你为什么这么关心列表是如何分配的呢?如果你需要知道某物的大小,可以使用sys.getsizeof()。@andrew cooke:请把它当作
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
*/
new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
/* check for integer overflow */
if (new_allocated > PY_SIZE_MAX - newsize) {
PyErr_NoMemory();
return -1;
} else {
new_allocated += newsize;
}
allocated = 0
for newsize in range(0,100,1):
if (allocated < newsize):
new_allocated = (newsize >> 3) + (3 if newsize < 9 else 6)
allocated = newsize + new_allocated;
print newsize, allocated