list()使用的内存略高于列表解析

2024-05-04 23:59:06 发布

您现在位置:Python中文网/ 问答频道 /正文

所以我在玩list对象时发现了一个小奇怪的现象,如果list是用list()创建的,它使用的内存比列表理解要多?我使用的是python3.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

Lists may be constructed in several ways:

  • Using a pair of square brackets to denote the empty list: []
  • Using square brackets, separating items with commas: [a], [a, b, c]
  • Using a list comprehension: [x for x in iterable]
  • Using the type constructor: list() or list(iterable)

但似乎使用list()会占用更多内存。在

list越大,间隙越大。在

Difference in memory

为什么会这样?在

更新1

使用Python 3.6.0b2进行测试:

^{pr2}$

更新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

Tags: 内存inimporttruefortypesysrange
2条回答

我想你看到的是分配模式这是一个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的列表理解的大小可以看到匹配的模式:

^{pr2}$

结果(格式为(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()可以。这意味着,在使用过度分配填充列表时,理解将不断增加列表,直到最终填充为止。在

一旦完成,is可能不会增加具有未使用的已分配节点的过度分配缓冲区(事实上,在大多数情况下,它不会,这将破坏过度分配的目的)。在

但是,list()可以添加一些缓冲区,不管列表大小如何,因为它预先知道最终的列表大小。在


另一个支持性证据(同样来自源代码)是,我们看到了list comprehensions invoking ^{},它表示使用list.resize,而这又表示在不知道将填充多少预分配缓冲区的情况下使用它。这和你看到的行为是一致的。在


总之,list()将根据列表大小预先分配更多节点

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

List comprehension不知道列表的大小,因此它在增长时使用append操作,耗尽预分配缓冲区:

# 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正确指出的:“list()决定了列表大小”。从图中可以看出。在

graph of sizes

当你append或使用列表理解时,你总会有某种边界,当你到达某个点时,这些边界就会延伸。使用list()你有几乎相同的边界,但是它们是浮动的。在

更新

感谢@ReutSharabani@tavo@SvenFestersen

总而言之:list()根据列表大小预先分配内存,列表理解无法做到这一点(它在需要时请求更多内存,如.append())。这就是list()存储更多内存的原因。在

还有一个图,显示list()预分配内存。所以绿线显示list(range(830))一个元素地追加元素,并且在一段时间内内存没有改变。在

list() preallocates memory

更新2

正如@Barmar在下面的评论中指出的,list()必须比列表理解快,所以我用number=1000运行timeit(),从4**0到{},结果是

time measurements

相关问题 更多 >