内存中列表的大小

hal*_*lex 62 python python-3.x

我刚刚在内存中试验了python数据结构的大小.我写了以下片段:

import sys
lst1=[]
lst1.append(1)
lst2=[1]
print(sys.getsizeof(lst1), sys.getsizeof(lst2))
Run Code Online (Sandbox Code Playgroud)

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

  • Windows 7 64位,Python3.1:输出为:52 40所以lst1有52个字节,lst2有40个字节.
  • Ubuntu 11.4 32bit with Python3.2:输出是 48 32
  • Ubuntu 11.4 32位Python2.7: 48 36

任何人都可以向我解释为什么两个尺寸不同虽然两个都是包含1的列表?

在getsizeof函数的python文档中,我发现了以下内容:...adds an additional garbage collector overhead if the object is managed by the garbage collector.在我的小例子中可能是这种情况吗?

Eli*_*sky 122

这是一个更全面的交互式会话,可以帮助我解释发生了什么(Windows XP 32位上的Python 2.6,但它确实无关紧要):

>>> import sys
>>> sys.getsizeof([])
36
>>> sys.getsizeof([1])
40
>>> lst = []
>>> lst.append(1)
>>> sys.getsizeof(lst)
52
>>> 
Run Code Online (Sandbox Code Playgroud)

请注意,空列表略小于其中的列表[1].但是,当附加元素时,它会变得更大.

原因是Objects/listobject.cCPython源代码中的实现细节.

空列表

[]创建空列表时,不会分配任何元素空间 - 这可以在中看到PyList_New.36字节是32位计算机上列表数据结构本身所需的空间量.

列出一个元素

[1]创建具有单个元素的列表时,除了列表数据结构本身所需的内存之外,还分配一个元素的空间.再次,这可以在中找到PyList_New.鉴于size作为参数,它计算:

nbytes = size * sizeof(PyObject *);
Run Code Online (Sandbox Code Playgroud)

然后有:

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;
Run Code Online (Sandbox Code Playgroud)

所以我们看到,size = 1分配了一个指针的空间.4个字节(在我的32位盒子上).

附加到空列表

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

  • PyList_Append 电话 app1
  • app1 要求列表的大小(并得到0作为答案)
  • app1然后调用list_resizesize+1(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;
}
Run Code Online (Sandbox Code Playgroud)

我们来做一些数学

让我们看看我在文章开头的会话中引用的数字是如何达到的.

因此,36字节是列表数据结构本身在32位上所需的大小.使用单个元素,为一个指针分配空间,因此4个额外字节 - 总共40个字节.好到目前为止.

app1在空列表上调用list_resize时,它会调用size=1.根据过度分配算法list_resize,1之后的下一个最大可用大小为4,因此将分配4个指针的位置.4*4 = 16字节,36 + 16 = 52.

确实,一切都有道理:-)


and*_*oke 9

对不起,以前的评论有点简短.

发生了什么事情,你正在研究如何分配列表(我想也许你只是想看看有多大的东西 - 在这种情况下,使用sys.getsizeof())

当某些内容添加到列表中时,可能会发生以下两种情况之一:

  1. 额外的物品适合备用空间

  2. 需要额外的空间,因此会创建一个新列表,并复制内容,并添加额外的内容.

因为(2)是昂贵的(复制东西,甚至是指针,需要时间与要复制的东西的数量成比例,所以随着列表变大而增长)我们想要不经常这样做.所以我们不是只添加更多的空间,而是添加一大块空间.通常,添加量的大小与已经使用的大小类似 - 数学计算得出分配内存的平均成本(分散在许多用途中)仅与列表大小成比例.

所以你所看到的与这种行为有关.我不知道确切的细节,但如果[][1](或两者)都是特殊情况,我会不会感到惊讶,只分配了足够的内存(在这些常见情况下节省内存),然后附加"抓住一个上面描述的新组块增加了更多.

但我不知道确切的细节 - 这就是动态数组的工作原理.python中列表的确切实现将进行微调,以便它适用于典型的python程序.所以我真正说的是你不能相信列表的大小来确切地告诉你它包含了多少 - 它可能包含额外的空间,并且额外的自由空间量很难判断或预测.

ps一个简洁的替代方法是将列表作为(value, pointer)对,其中每个指针指向下一个元组.通过这种方式,您可以逐步增加列表,尽管使用的总内存更高.这是一个链表(python使用的更像是矢量或动态数组).

[更新]见Eli的优秀答案.他/她解释说,这两个[][1]是精确分配,但追加到[]分配额外的块.代码中的注释就是我上面所说的(这称为"过度分配",数量与我们所拥有的相对应,因此平均("摊销")成本与大小成正比).

  • @Ned:-1,真的吗?当然,这不是官方术语,但它最多只是一个微小的差异(在这里甚至没有做到一致,所以它可能只是一个滑点).并且它没有错,请注意 - Python称之为"list"*是*编程世界的其他人所知道的数组.(编辑:有点迟了......好吧,点仍然有效,我不知道这怎么可能有理由-1) (5认同)
  • 术语很重要,Python有一个"数组"模块,Numpy提供数组.人们使用Python的许多困难是由于假装其他语言.更好地鼓励本机Python思考.很高兴看到编辑,我收回了我的-1. (3认同)
  • @Ned:我还没有看到原版,但Python*中的列表是用动态数组实现的(相反,比如链接列表) (2认同)