所以我在玩list
对象,发现一点奇怪的事情是,如果用list
创建list()
,则比列表理解要使用更多的内存?我正在使用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
从docs:
列表可以通过几种方式构造:
- 使用一对方括号表示空白列表:
[]
- 使用方括号,用逗号分隔项目:
[a]
,[a, b, c]
- 使用列表理解:
[x for x in iterable]
- 使用类型构造函数:
list()
或list(iterable)
但是似乎使用list()
会占用更多的内存。
list
越大,间隙越大。
为什么会这样?
UPDATE#1
使用Python 3.6.0b2测试:
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
UPDATE#2
使用Python 2.7.12测试:
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
我认为您正在看到过度分配模式,这是一个sample from the source:
/* 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))
出于性能原因而进行了过度分配,允许列表增长而每次增长都没有分配更多的内存(更好的amortized性能。]
使用列表理解的差异的可能原因是,列表理解不能确定性地计算生成列表的大小,但是list()
可以。这意味着,在使用过度分配填充列表的过程中,理解力将不断增长,直到最终填充它。
[一旦完成,很可能不会在未分配的节点上增加过度分配缓冲区(实际上,在大多数情况下,不会破坏过度分配的目的)。
但是,list()
可以添加一些缓冲区,无论列表大小如何,因为它事先知道最终的列表大小。
[从源头获得的另一个支持证据是,我们看到list comprehensions invoking LIST_APPEND
,它表示LIST_APPEND
的使用,这又表明在不知道将要填充多少的情况下消耗了预分配缓冲区。这与您看到的行为一致。
最后,list.resize
将根据列表大小预分配更多节点
list()
列表推导不知道列表的大小,因此随着列表的增长,它会使用追加操作,从而耗尽了预分配缓冲区:
>>> sys.getsizeof(list([1,2,3]))
60
>>> sys.getsizeof(list([1,2,3,4]))
64
感谢大家帮助我理解了这个很棒的Python。
我不想提出这么大的问题(为什么我要发布答案),只想展示和分享我的想法。
如# 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
正确指出的那样:“ list()确定性地确定列表大小”。您可以从该图中看到它。
或使用列表理解时,总会遇到某种界限,当您到达某个点时就会扩展。 append
的边界几乎相同,但它们是浮动的。
UPDATE
感谢list()
,@ReutSharabani,@tavo
总结:@SvenFestersen根据列表大小预先分配内存,列表理解无法做到这一点(它需要时会请求更多的内存,例如list()
)。这就是.append()
存储更多内存的原因。
另外一张图表,显示list()
预分配了内存。因此,绿线显示list()
逐个元素追加,一段时间后内存没有变化。
list(range(830))
UPDATE 2
正如@Barmar在下面的评论中指出的,必须比列表理解更快,因此我将list()
和timeit()
的长度从number=1000
到list
运行了4**0
,结果是
4**10