list()使用比列表理解更多的内存

所以我在玩list对象,发现了一些奇怪的事情,如果使用list()创buildlist()它比列表理解使用更多的内存? 我正在使用Python 3.5.2

 In [1]: import sys In [2]: a = list(range(100)) In [3]: sys.getsizeof(a) Out[3]: 1008 In [4]: b = [i for i in range(100)] In [5]: sys.getsizeof(b) Out[5]: 912 In [6]: type(a) == type(b) Out[6]: True In [7]: a == b Out[7]: True In [8]: sys.getsizeof(list(b)) Out[8]: 1008 

从文档 :

列表可能以几种方式构build:

  • 用一对方括号表示空列表: []
  • 使用方括号,用逗号分隔项目: [a][a, b, c]
  • 使用列表理解: [x for x in iterable]
  • 使用types构造函数: list()或者list(iterable)

但似乎使用list()它使用更多的内存。

而且尽可能多的list越大,差距就越大。

记忆力差异

为什么会发生?

更新#1

用Python 3.6.0b2testing:

 Python 3.6.0b2 (default, Oct 11 2016, 11:52:53) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.getsizeof(list(range(100))) 1008 >>> sys.getsizeof([i for i in range(100)]) 912 

更新#2

用Python 2.7.12testing:

 Python 2.7.12 (default, Jul 1 2016, 15:12:24) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.getsizeof(list(xrange(100))) 1016 >>> sys.getsizeof([i for i in xrange(100)]) 920 

我认为你看到过度分配模式,这是来自源代码的示例 :

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

打印长度为0-88的列表推导的大小可以看到模式匹配:

 # create comprehensions for sizes 0-88 comprehensions = [sys.getsizeof([1 for _ in range(l)]) for l in range(90)] # only take those that resulted in growth compared to previous length steps = zip(comprehensions, comprehensions[1:]) growths = [x for x in list(enumerate(steps)) if x[1][0] != x[1][1]] # print the results: for growth in growths: print(growth) 

结果(格式是(list length, (old total size, new total size)) ):

 (0, (64, 96)) (4, (96, 128)) (8, (128, 192)) (16, (192, 264)) (25, (264, 344)) (35, (344, 432)) (46, (432, 528)) (58, (528, 640)) (72, (640, 768)) (88, (768, 912)) 

超额分配是出于性能原因而完成的,允许列表在不增加每个增长(更好的摊销性能)的情况下分配更多内存。

与使用列表理解差异的一个可能的原因是列表理解不能确定性地计算生成的列表的大小,而list()能够。 这意味着理解不断增加列表,因为它使用过度分配来填充,直到最终填充它。

一旦完成(实际上,在大多数情况下,它不会超出分配的目的),将不会增加带有未使用分配节点的超额分配缓冲区。

然而, list()可以添加一些缓冲区而不pipe列表大小,因为它事先知道最终的列表大小。


另一个来自源的支持证据是,我们看到列表LIST_APPEND调用LIST_APPEND ,它指示list.resize使用,这反过来指示使用预分配缓冲区,而不知道它将被填充多less。 这与你所看到的行为是一致的。


总之, list()将预先分配更多的节点作为列表大小的函数

 >>> sys.getsizeof(list([1,2,3])) 60 >>> sys.getsizeof(list([1,2,3,4])) 64 

列表理解不知道列表大小,因此它使用追加操作,因为它增长,耗尽预分配缓冲区:

 # one item before filling pre-allocation buffer completely >>> sys.getsizeof([i for i in [1,2,3]]) 52 # fills pre-allocation buffer completely # note that size did not change, we still have buffered unused nodes >>> sys.getsizeof([i for i in [1,2,3,4]]) 52 # grows pre-allocation buffer >>> sys.getsizeof([i for i in [1,2,3,4,5]]) 68 

谢谢大家帮助我理解那真棒的Python。

我不想提出质疑(为什么我张贴答案),只是想显示和分享我的想法。

正如@ReutSharabani正确指出:“列表()确定性地确定列表大小”。 你可以从该图中看到它。

尺寸图

当你append或使用列表理解的时候,你总是会遇到一些延伸到某个点的边界。 而与list()你有几乎相同的界限,但他们是浮动的。

UPDATE

所以感谢@ReutSharabani , @tavo , @SvenFestersen

总结一下: list()预先分配内存取决于列表大小,list理解不能做到这一点(它需要更多的内存,如.append() )。 这就是为什么list()存储更多的内存。

再看一个图,显示list()预先分配内存。 所以绿线显示list(range(830))逐元素追加和一段时间内存不变。

list()预先分配内存

更新2

正如@Barmar在下面的注释中指出的, list()必须比列表理解更快,所以我运行了timeit() ,其number=1000list长度从4**04**10 ,结果是

实时测量