python-3.x 内存中列表的大小

uinbv5nw  于 2023-01-22  发布在  Python
关注(0)|答案(4)|浏览(139)

我刚刚试验了内存中python数据结构的大小,我写了下面的代码片段:

import sys
lst1=[]
lst1.append(1)
lst2=[1]
print(sys.getsizeof(lst1), sys.getsizeof(lst2))

我在以下配置上测试了代码:

  • Windows 7 64位,Python 3.1:输出为:52 40,因此LST1具有52字节,而LST2具有40字节。
  • Ubuntu 11.4 32位版本,带Python 3.2:输出为48 32
  • Ubuntu 11.4 32位Python 2.7:48 36

谁能给我解释一下为什么这两个大小不同,尽管它们都是包含1的列表?
在getsizeof函数的python文档中,我发现了以下内容:...adds an additional garbage collector overhead if the object is managed by the garbage collector.在我的小例子中会是这种情况吗?

amrnrhlw

amrnrhlw1#

下面是一个更完整的互动会话,它将帮助我解释正在发生的事情(Python 2.6在Windows XP 32位上,但这并不重要):

>>> import sys
>>> sys.getsizeof([])
36
>>> sys.getsizeof([1])
40
>>> lst = []
>>> lst.append(1)
>>> sys.getsizeof(lst)
52
>>>

注意,空列表比包含[1]的列表要小一些,但是当添加了一个元素时,它会变得更大。
其原因是CPython源代码中Objects/listobject.c的实现细节。

空列表

当创建一个空列表[]时,没有为元素分配空间--这可以在PyList_New中看到。36字节是32位机器上列表数据结构本身所需的空间量。

包含一个元素的列表

当创建一个包含单个元素[1]的列表时,除了列表数据结构本身所需的内存之外,还为一个元素分配空间,这同样可以在PyList_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;

因此,我们看到size = 1为一个指针分配了4字节的空间(在我的32位机器上)。

追加到空列表

在空列表上调用append时,会发生以下情况:

  • PyList_Append调用app1
  • app1询问列表的大小(并得到0作为回答)
  • 然后app1size+1调用list_resize(在我们的例子中为1)
  • list_resize有一个有趣的分配策略,在这篇评论中从它的来源进行了总结。

这就是:

/* 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;
}

让我们做一些数学

让我们看看我在文章开头的会话中引用的数字是如何达到的。
所以36字节是32位列表数据结构本身所需的大小,对于单个元素,空间被分配给一个指针,所以有4个额外的字节--总共40字节,到目前为止还不错。
app1在空列表上被调用时,它用size=1调用list_resize。根据list_resize的过度分配算法,1之后的下一个最大可用大小是4,因此将分配4个指针的位置。4 * 4 = 16字节,并且36 + 16 = 52。
的确,一切都说得通:-)

wf82jlnq

wf82jlnq2#

抱歉,之前的评论有点草率。
实际情况是,您正在查看列表是如何分配的(我想您可能只是想查看列表有多大-在这种情况下,使用sys.getsizeof()
当向列表中添加某些内容时,可能发生以下两种情况之一:
1.多余的物品可以放在多余的空间里
1.需要额外的空间,所以制作了一个新的列表,将内容复制过来,并添加额外的内容。
因为(2)价格昂贵(复制东西,甚至指针,所花费的时间与要复制的东西的数量成比例,所以随着列表变大而增长)我们希望不频繁地这样做。所以不是仅仅添加多一点空间,而是添加整个块。通常添加的量的大小与已经在使用的量的大小相似-这样数学计算出分配存储器的平均成本,在许多使用中展开,仅与列表大小成比例。
所以你所看到的是与这个行为相关的。我不知道确切的细节,但是如果[][1](或者两者)是特殊情况,我不会感到惊讶,其中只分配了足够的内存(在这些常见情况下保存内存),然后appending执行上面描述的“抓取一个新块”,添加更多。
但是我不知道确切的细节--这只是动态数组一般的工作方式。列表在python中的确切实现将被精细地调整,以便它对于典型的python程序是最优的。所以我真正要说的是,你不能相信列表的大小来告诉你它到底包含了多少--它可能包含额外的空间,并且额外空闲空间的量难以判断或预测。
ps一个简洁的替代方法是把列表做成(value, pointer)对,其中每个指针指向下一个元组。这样你就可以逐渐增加列表,尽管使用的总内存更高。这是一个链表(python使用的更像一个向量或动态数组)。
[update]参见Eli的精彩回答。他们解释说[][1]都是精确分配的,但是追加到[]分配了额外的块。代码中的注解就是我上面所说的(这被称为“过度分配”,其数量与我们拥有的数量成比例,因此平均(“摊销”)成本与大小成比例)。

5anewei6

5anewei63#

这里有一个列表增长模式的快速演示,改变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
e4yzc0pl

e4yzc0pl4#

公式根据系统体系结构更改(size-36)/4(对于32位计算机)和(size-64)/8(对于64位计算机
36,64-基于计算机的空列表的大小4,8-基于计算机的列表中单个元素的大小

相关问题