2015-09-05 17 views
8

我用%memit神奇的功能來衡量內存使用:內存使用:創建一個大集VS合併許多小套

In [1]: %memit n = pow(10, 7); range(n) 
peak memory: 568 MiB, increment: 272 MiB 

In [2]: %memit n = pow(10, 7); set(xrange(n)) 
peak memory: 824 MiB, increment: 447 MiB 

行,所以似乎有哪裏xrange(n)實例化爲一個完整列表的一箇中間步驟。但是如果我把我的列表切成10個子列表並將它們一個一個地聯合起來呢?這會更有效率的記憶,對嗎?

In [3]: %memit n = pow(10, 7); reduce(set.union, (set(xrange(p, n, 10)) for p in range(10))) 
peak memory: 1260 MiB, increment: 897 MiB 

那麼,這並沒有如預期那樣。爲什麼reduce方法比set(xrange(n))消耗更多內存?

+1

相關問題:http://stackoverflow.com/questions/15198042/why-does-union-consume-more-memory-if-the-argument-is-a-set請注意,'set.union'將使用**如果參數是一個集合,則爲更多**內存,因爲它假定將會有少量通用元素,因此它將分配所需內存的兩倍。 – Bakuriu

回答

3

有很多誤解需要清除。首先,一個xrange而不是set(xrange(n))變成set之前變成了一個列表!

峯值內存reduce方法來自於事實,set.union返回值是2分的結果集的聯合。並在reduce內部存儲一個額外的值,accum_value。所以對for循環的最後一次迭代:

accum_value = function(accum_value, x) 

是進入功能accum_value含有90%10⁷元素,x包含10⁷元素的10%,但返回值將需要空間所有10⁷元素。所有這些空間需要同時保留。只有在function已返回之後,纔會發佈舊accum_valuex的內存。


但是,這還沒有結束。峯值內存確實需要多少內存的過程中,但沒有多少在使用中之後操作完成,如果該集合仍然存在。

因此,需要一個不同的基準:

In [1]: %memit n = pow(10, 7); result = set(xrange(n)); 
peak memory: 522.22 MiB, increment: 498.93 MiB 
In [2]: %memit 42 
peak memory: 513.83 MiB, increment: 0.12 MiB 
In [3]: import sys 
In [4]: sys.getsizeof(result) 
Out[4]: 268435688 

In [1]: %memit n = pow(10, 7); result = reduce(set.union, (set(xrange(p, n, 10)) for p in range(10))); 
peak memory: 1063.20 MiB, increment: 1039.71 MiB 
In [2]: %memit 42 
peak memory: 779.77 MiB, increment: 0.00 MiB 
In [3]: import sys  
In [4]: sys.getsizeof(result) 
Out[4]: 536871144 
In [5]: 536871144.0/268435688 
Out[5]: 1.9999991357333977 

所以對於reduce存儲器使用到MIB 778執行後下降;然而,它仍然比更簡單的設置構建案例更多。正如你所看到的,set(xrange(n))並不需要太多的內存,因爲內存使用量在構建完畢後僅僅下降了9 MiB。

最值得注意的不僅是reduce方法更慢;結果集也消耗兩倍的內存。這是因爲一個集合由一個散列表支持,並且每當它被認爲具有太多衝突時它就會變大。你已經遇到了病態行爲,其中一組相同的值導致一個內存比另一個多兩倍。

4

Per the docsreduce大致相當於:

def reduce(function, iterable, initializer=None): 
    it = iter(iterable) 
    if initializer is None: 
     try: 
      initializer = next(it) 
     except StopIteration: 
      raise TypeError('reduce() of empty sequence with no initial value') 
    accum_value = initializer 
    for x in it: 
     accum_value = function(accum_value, x) 
    return accum_value 

遍歷iterable(set(xrange(p, n, 10)) for p in range(10)), 需要約447 MIB。 你可能會認爲,因爲這可迭代是一個生成器表達式,你會節省內存,但整數是saved in an internal free list

「飛車」,Python的維護整數對象的內部空閒列表。不幸的是,這份免費清單既不朽又無限大。

所以一旦每組已被實例化,最消耗內存永遠不會被釋放。

返回的值accum_value也需要約447 MiB。因此,呼叫reduce因此需要大約447 + 447 = 894兆比特。