python - list() 使用比列表理解稍多的内存

标签 python list list-comprehension cpython python-internals

所以我在玩 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 :

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 进行测试:

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.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)

结果(格式为(列表长度,(旧总大小,新总大小))):

(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.resize 的使用,这反过来表示在不知道将填充多少的情况下消耗预分配缓冲区。这与您看到的行为一致。


总而言之,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 - list() 使用比列表理解稍多的内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40018398/

相关文章:

javascript - 在 Django 中的两个自定义标签之间传递变量

python - 来自石榴贝叶斯网络的样本

python - 在输入中连接的列表和文本

c# - 在列表中搜索组合

python - “列表”对象在列表理解中不可调用

python - 在 DynamoDB 中存储推文

Python 和列表理解的性能

python - 为什么交换奇数个变量列表时变量会消失?

python - 在 Python 中,我如何使用列表理解来遍历列表列表?

erlang - 将二进制文件分成 block 的更好方法,最好使用位串理解