为什么两个相同的列表具有不同的内存占用量?

时间:2018-07-25 19:22:18

标签: python list memory-management python-internals

我创建了两个列表l1l2,但是每个列表都有不同的创建方法:

import sys

l1 = [None] * 10
l2 = [None for _ in range(10)]

print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))

但是输出使我感到惊讶:

Size of l1 = 144
Size of l2 = 192

使用列表推导创建的列表在内存中更大,但是在Python中这两个列表相同。

那是为什么?这是CPython内部的东西,还是其他解释?

3 个答案:

答案 0 :(得分:149)

编写[None] * 10时,Python知道它将需要一个正好包含10个对象的列表,因此它会精确地分配该对象。

当您使用列表推导时,Python不知道它将需要多少。因此,随着元素的添加,列表逐渐增加。对于每个重新分配,它分配的空间都超过了立即需要的空间,因此不必为每个元素重新分配。结果列表可能会比需要的大一些。

比较以类似大小创建的列表时,您会看到此行为:

>>> sys.getsizeof([None]*15)
184
>>> sys.getsizeof([None]*16)
192
>>> sys.getsizeof([None for _ in range(15)])
192
>>> sys.getsizeof([None for _ in range(16)])
192
>>> sys.getsizeof([None for _ in range(17)])
264

您可以看到第一种方法只分配需要的内容,而第二种则周期性地增长。在此示例中,它为16个元素分配了足够的资源,并且在达到第17个元素时不得不重新分配。

答案 1 :(得分:45)

this question中所述,列表理解在幕后使用list.append,因此它将调用list-resize方法进行汇总。

要向自己证明这一点,您实际上可以使用dis反汇编程序:

>>> code = compile('[x for x in iterable]', '', 'eval')
>>> import dis
>>> dis.dis(code)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x10560b810, file "", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (iterable)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x10560b810, file "", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (x)
              8 LOAD_FAST                1 (x)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
>>>

LIST_APPEND代码对象的反汇编中,请注意<listcomp>操作码。来自docs

  

LIST_APPEND(i)

     

呼叫list.append(TOS[-i], TOS)。用于实现列表推导。

现在,对于列表重复操作,如果考虑以下因素,我们会提示发生了什么情况:

>>> import sys
>>> sys.getsizeof([])
64
>>> 8*10
80
>>> 64 + 80
144
>>> sys.getsizeof([None]*10)
144

因此,似乎可以准确分配大小。看着source code,我们看到的就是这样:

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    Py_ssize_t i, j;
    Py_ssize_t size;
    PyListObject *np;
    PyObject **p, **items;
    PyObject *elem;
    if (n < 0)
        n = 0;
    if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
        return PyErr_NoMemory();
    size = Py_SIZE(a) * n;
    if (size == 0)
        return PyList_New(0);
    np = (PyListObject *) PyList_New(size);

即,这里:size = Py_SIZE(a) * n;。其余函数仅填充数组。

答案 2 :(得分:3)

没有一个内存块,但是它不是预先指定的大小。除此之外,数组元素之间的数组还有一些额外的间距。您可以通过运行以下内容自己查看:

for ele in l2:
    print(sys.getsizeof(ele))

>>>>16
16
16
16
16
16
16
16
16
16

不是总和为l2,而是更小。

print(sys.getsizeof([None]))
72

这比l1的十分之一大。

您的数字应该有所不同,具体取决于您的操作系统的详细信息和操作系统中当前内存使用的详细信息。 [None]的大小永远不能大于将变量设置为存储的可用相邻内存,并且如果以后将其动态分配为更大,则可能必须移动该变量。