Python 为什么两个相同的列表具有不同的内存占用?
我创建了两个列表Python 为什么两个相同的列表具有不同的内存占用?,python,list,memory-management,python-internals,Python,List,Memory Management,Python Internals,我创建了两个列表l1和l2,但每个列表的创建方法不同: 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中这两个列
l1
和l2
,但每个列表的创建方法不同:
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内部的东西还是其他解释?当您编写
[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个元素时必须重新分配。如列表中所述,它使用列表。在引擎盖下追加,因此它将调用列表调整大小方法,该方法会过度分配
为了向自己演示这一点,您可以实际使用dis
dissasembler:
>>> 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
>>>
因此,它似乎能够准确地分配大小。看一看,我们发现这正是发生的情况:
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);
静态PyObject*
列表重复(PyListObject*a,Py\u ssize\n)
{
Py_ssize_t i,j;
尺寸;
PyListObject*np;
PyObject**p,**项;
PyObject*elem;
if(n<0)
n=0;
如果(n>0&&Py\u SIZE(a)>Py\u-SSIZE\u-T\u MAX/n)
返回PyErr_nomery();
尺寸=Py_尺寸(a)*n;
如果(大小==0)
返回PyList_New(0);
np=(PyListObject*)PyList_新(尺寸);
也就是说,这里:size=Py_size(a)*n;
。其余的函数只是填充数组。无是内存块,但不是预先指定的大小。此外,数组元素之间还有一些额外的间距。您可以通过运行:
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
大小的十分之一
您的数字应根据操作系统的详细信息和操作系统中当前内存使用情况的详细信息而有所不同。[无]的大小不能大于设置为存储变量的可用相邻内存,并且如果以后动态分配该变量以使其更大,则可能必须移动该变量。重复运算符可能会调用某些函数来精确调整基础数组的大小。请注意,144==sys.getsizeof([])+8*10)
其中8是指针的大小。请注意,如果将10
更改为11
,则[无]*11
列表有大小152
,但列表理解仍然有大小192
。前面链接的问题不是完全重复的,但它与理解为什么会发生这种情况有关。是的,这很有意义。当我知道前面的大小时,可能最好使用*
创建列表。@AndrejKesely只使用[x]*n
在列表中包含不可变的x
。生成的列表将包含对相同对象的引用。@schwobasegll好吧,这可能是您想要的,但很好地理解了这一点。@juanpa.arrivillaga可能是真的。但通常不是,尤其是满是想知道为什么所有数据同时更改的海报ly:D“正如在这个问题中提到的,列表理解使用list.append-under-the-hood”我认为更准确的说法是它使用.extend()
@accumulation你为什么这么认为?因为它不是一个接一个地追加元素。当你将元素追加到列表中时,你实际上是在创建一个新的列表,并使用新的内存分配,然后将该列表放入新的内存分配中。另一方面,列表理解将大部分新元素放入已经存在的内存中n已分配,并且当分配的内存用完时,它们会分配另一个内存卡盘,这不足以容纳新元素。@accumulation这是不正确的。list.append
是一个摊销的常量时间操作,因为当列表调整大小时,它会过度分配。因此,并非每个append操作都会导致新分配的array、 无论如何,我链接到的问题在源代码中向您展示了,事实上,列表理解确实使用了list.append
,我会马上回到我的笔记本电脑,我可以向您展示用于列表理解的反汇编字节码,相应的list\u append
操作码None
实际上并不存储在在底层数组中,唯一存储的是一个PyObject
指针(8字节)。所有Python对象都分配在堆上。None
是一个单例,因此拥有一个包含多个None的列表将创建一个PyObject指针数组,指向堆上相同的None
对象(并且不要在每个额外的None
)的过程中使用额外的内存。我不确定你所说的“None没有预先指定的大小”是什么意思,但这听起来并不正确。最后,你与getsizeof
每个元素的循环并没有显示你认为它正在显示的内容。如果你说的是真的,那么[None]的大小*10应该与[None]的大小相同。但显然不是这样——添加了一些额外的存储。事实上,重复十次(160)的[None]的大小也小于[None]乘以10的大小。正如您所指出的,指向[None]的指针的大小显然小于[None]本身的大小(16字节而不是72字节)。然而,160+32是192。我不认为p
print(sys.getsizeof([None]))
72