2016-10-13 19 views
72

所以我玩list對象,發現一點奇怪的事情,如果list創建與list()它使用更多的內存,比列表理解?我使用Python 3.5.2list()使用比列表理解更多的內存

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 

更新#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 
+3

這是一個非常有趣的問題。我可以在Python 3.4.3中重現這一現象。甚至更有趣的:關於Python 2.7.5'sys.getsizeof(列表(範圍(100)))'是1016,'getsizeof(範圍(100))'是872和'getsizeof([I爲i的範圍(100) ])'是920.所有的都有'list'類型。 –

+0

感興趣的是,Python 2.7.10中也存在這種差異(儘管實際數字與Python 3不同)。還有在3.5和3.6b。 – cdarke

+0

當使用'xrange'時,我得到的Python 2.7.6與@SvenFestersen相同。 – RemcoGerlich

回答

53

我想你正在看o VER-分配模式,這是一個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 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 
+4

但爲什麼過度分配會發生在一個而不是另一個? – cdarke

+0

這個具體來自'list.resize'。我不是一位在源頭上導航的專家,但是如果一個人調用調整大小,而另一個則不調整 - 它可以解釋不同之處。 –

+5

Python 3.5.2在這裏。嘗試在循環中打印列表大小從0到35。對於表I,參見'64,96,104,112,120,128,136,144,160,192,200,208,216,224,232,240,256,264,272,280,288,296,304 ,312,328,336,344,352,360,368,376,384,400,408,416'和用於理解'64,96,96,96,96,128,128,128,128,192,192 ,192,192,192,192,192,192,264,264,264,264,264,264,264,264,264,344,344,344,344,344,344,344,344,344'中的一個或多個。除了理解力是那些似乎預先分配內存的人成爲對特定大小使用更多內存的算法之外我纔會理解。 – tavo

26

感謝大家幫助我理解那真棒Python。

我不想讓大量問題(爲什麼我發佈答案),只是想顯示和分享我的想法。

由於@ReutSharabani正確指出:「list()確定性地確定列表大小」。你可以從該圖中看到它。

graph of sizes

當你append或使用列表理解,你總是有某種界限的,當你到達某個點延伸。與list()你有幾乎相同的界限,但他們是浮動的。

UPDATE

所以感謝@ReutSharabani@tavo@SvenFestersen

綜上所述:list()預分配內存取決於列表大小,列表理解不能做到這一點(它請求時,它需要像更多的內存.append())。這就是爲什麼list()存儲更多的內存。

再一個圖,顯示list()預分配內存。所以綠線顯示list(range(830))每個元素都要追加一段時間,而內存不變。

list() preallocates memory

更新2

正如@Barmar在下面的評論指出,list()必須我比列表理解快,所以我跑timeit()number=1000list長度4**04**10和結果是

time measurements

+0

答案爲什麼紅線高於藍是,當'list'構造能確定的新名單大小從它的參數還是會預分配的空間相同的量,因爲它會如果最後一個元素只是到了那裏,並有沒有足夠的空間,至少這對我來說是有意義的。 – tavo

+0

@tavo對我來說它看起來是一樣的,過了一會兒我想在圖中顯示出來。 –

+1

因此,雖然列表推導使用較少的內存,但由於發生的所有調整大小,它們可能會顯着較慢。這些通常需要將列表骨幹複製到新的內存區域。 – Barmar

相關問題