2011-04-28 18 views
6

我正在測試幾種不同方式的速度來對我的一些數據執行復雜的迭代,並且發現了一些奇怪的東西。似乎有一個本地大的列表功能會大大減慢該功能,即使它沒有觸及該列表。例如,通過相同發生器函數的兩個實例創建2個獨立列表的次數大約是第二次減慢的2.5倍。如果在創建第二個列表之前刪除第一個列表,則兩個迭代器都以相同的速度進行。Python函數因大列表的存在而變慢

def f(): 
    l1, l2 = [], [] 
    for c1, c2 in generatorFxn(): 
     l1.append((c1, c2)) 
    # destroying l1 here fixes the problem 
    for c3, c4 in generatorFxn(): 
     l2.append((c3, c4)) 

這些清單最終每件約有310萬件物品,但我也看到了與小清單相同的效果。第一個for循環需要約4.5秒運行,第二個需要10.5。如果我在評論位置插入l1= []l1= len(l1),則兩個for循環都需要4.5秒。

爲什麼函數中局部內存分配的速度與該函數變量的當前大小有什麼關係?

編輯: 禁用垃圾收集器修復了一切,所以必須由於它不斷運行。案件結案!

回答

9

當您創建許多新對象(300萬個元組)時,垃圾回收器陷入困境。如果使用gc.disable()關閉垃圾收集,問題就會消失(程序運行速度提高4倍以便啓動)。

+0

正確的你是先生,禁用它會下降4.5秒到1.3秒,並幾乎消除了差異(第二個仍然稍微慢一點,但不再多)。爲什麼垃圾收集器運行得如此緩慢,即使在創建一個大型列表之後,它仍然存在,沒有被修改?一旦函數返回,它不應該只有工作嗎? – DaveTheScientist 2011-04-28 19:50:10

+0

另外,如果'l'是一個類變量而不是局部變量,爲什麼減速會消失? – DaveTheScientist 2011-04-28 19:53:12

+0

下面是我最好的猜測:垃圾收集器定期運行以收集舊對象。它通過比較自上次收集以來分配和釋放對象的數量來確定何時運行。如果分配 - 取消分配>閾值,則收集器運行(默認情況下閾值= 700)。由於每次迭代創建(至少)1個新對象,收集器運行3e6/700 = 4285次。這實際上減慢了兩次迭代的速度,但第二次迭代速度較慢,因爲收集器需要檢查更多的對象。 – Luke 2011-04-29 01:25:01

0

函數本地數據使用的內存不會被垃圾收集,直到函數返回。除非你需要做切片,否則使用列表來處理大量數據並不是一個好主意。

從你的例子中不清楚創建這些列表的目的是什麼。你可能要考慮使用生成器而不是列表,特別是如果列表剛剛迭代。如果您需要對返回數據進行分片,那麼將生成器轉換爲列表。

+0

創建這些列表最初只是爲了測試不同生成器函數的速度,但是一旦找到合適的算法,我就必須創建這些列表。問題不在於尋找更好的數據存儲方式,而是爲什麼存在如此巨大的性能影響。這對我來說毫無意義,我希望在將來進行編程時記住答案。 而且我知道垃圾收集不會在函數中間發生,這就是爲什麼重新分配'l'不應該解決問題(但它確實存在)。 – DaveTheScientist 2011-04-28 19:14:41

+0

感謝您的解釋! – jathanism 2011-04-29 14:34:23

2

沒有更詳細的儀器就不可能說出來。

作爲非常非常初步的步驟,請檢查您的主內存使用情況。如果你的RAM全部被佔滿,並且你的操作系統正在分頁到磁盤,你的性能將會非常糟糕。在這種情況下,您可能最好將您的中間產品放在其他地方,而不要放在記憶中。如果您只需要連續讀取數據,請考慮寫入純文件;如果您的數據遵循嚴格的結構,請考慮堅持關係數據庫。

+1

列表中的每個項目都是2個字符串的元組,每個元素長度爲3個字符。我已經看到小列表的效果相同。每個清單大小爲770,000,時間爲0.57和0.96秒。在188,000時,他們是0.11和0.15秒。我很確定這不是我穿越的內存門檻,因爲我在一個帶有4g內存的64位系統上。 – DaveTheScientist 2011-04-28 19:10:28

2

我的猜測是,當創建第一個列表時,會有更多的可用內存,這意味着列表需要隨着其增長而重新分配的可能性會降低。

在第一個列表佔用大量內存之後,第二個列表會有更高的 因爲python列表動態調整大小而需要重新分配的機會。

+0

+ 1爲了提高內存分配(通常)並不是一大塊。根據活動監視器,這兩個列表一起需要大約280mb的空間,所以這是一個相當大的塊。 – DaveTheScientist 2011-04-28 20:04:57