2009-11-16 54 views
9

的行爲我有一個Django應用程序表現出一些奇怪的垃圾收集行爲。特別是有一種觀點認爲,每次調用時都會顯着增加虛擬機的大小 - 達到某個限制,此時使用率會再次下降。問題是,直到達到這一點需要相當長的時間,事實上,運行我的應用程序的虛擬機沒有足夠的內存供所有FCGI進程佔用儘可能多的內存。的Python:垃圾收集

我花了最後兩天調查這和學習Python的垃圾收集,我想我明白現在發生的事情 - 大部分。當使用

gc.set_debug(gc.DEBUG_STATS) 

那麼對於一個請求,我看到下面的輸出:

>>> c = django.test.Client() 
>>> c.get('/the/view/') 
gc: collecting generation 0... 
gc: objects in each generation: 724 5748 147341 
gc: done. 
gc: collecting generation 0... 
gc: objects in each generation: 731 6460 147341 
gc: done. 
[...more of the same...]  
gc: collecting generation 1... 
gc: objects in each generation: 718 8577 147341 
gc: done. 
gc: collecting generation 0... 
gc: objects in each generation: 714 0 156614 
gc: done. 
[...more of the same...] 
gc: collecting generation 0... 
gc: objects in each generation: 715 5578 156612 
gc: done. 

所以基本上,一個巨大的物體的量分配,但最初轉移到第1,當根1在同一個請求期間被清除,它們被移到第二代。如果我之後做了一個手動gc.collect(2),它們將被刪除。而且,正如我所提到的那樣,當下一次自動生成第2代掃描時,它也會被刪除,如果我理解正確,在這種情況下,每10次請求就會有一次(此時應用程序需要大約150MB)。

好了,所以最初我認爲有可能是一些環狀引用其防止任何這些對象中的從處理請求內所收集一個請求的處理過程中繼續。然而,我花了數小時的時間試圖找到一個使用pympler.muppy和objgraph,在請求處理之後和之後進行調試,並且似乎沒有任何問題。相反,在請求期間創建的14.000個左右的對象似乎都是在某個請求全局對象的引用鏈內,即一旦請求消失,它們就可以被釋放。

這一直是我的企圖在解釋吧,反正。但是,如果這是真的,並確有沒有騎自行車的依賴關係,不應該對象的整個樹被釋放,一旦任何請求對象,使他們舉行消失,而不涉及垃圾收集,純粹憑藉引用計數的降至零?

使用這個配置,這裏是我的問題:

  • 請問以上,甚至是有意義的,還是我不得不尋找其他地方的問題?這是一個不幸的事故,重要的數據在這個特定的用例中保存了很長時間嗎?

  • 有什麼我可以做,以避免這個問題。我已經看到了優化視圖的一些潛力,但這似乎是一個範圍有限的解決方案 - 儘管我不確定我的通用解決方案是什麼。例如,如何手動調用gc.collect()或gc.set_threshold()?

在垃圾收集器本身的工作原理方面:

  • 難道我理解正確的話,一個對象總是移動到下一代,如果掃描看着它確定引用它有不是循環的,但實際上可以追溯到根對象。

  • 如果gc進行第1代掃描並發現第2代內的對象所引用的對象,會發生什麼情況;它是否遵循第二代內部的關係,還是在分析情況之前等待第二代掃描發生?

  • 當使用gc.DEBUG_STATS時,我主要關心「每一代中的對象」信息;然而,我不斷收到數百個「gc:0.0740秒過去了」,「gc:1258233035.9370s過去了。」消息;他們完全不方便 - 他們需要花費相當多的時間才能印出來,而且他們使得有趣的事情難以發現。有沒有辦法擺脫它們?

  • 我不假設有一種方法可以按代生成gc.get_objects(),例如只從第2代檢索對象,例如?

回答

2

我認爲你的分析看起來很合理。我不是gc的專家,所以無論何時我遇到這樣的問題,我只需在適當的,非時間要求很高的地方添加一個致電gc.collect(),然後忘掉它。

我建議你在你的視圖中調用gc.collect(),看看它對你的響應時間和你的內存使用有什麼影響。

還請注意this question這表明設置DEBUG=True吃它的內存,因爲它幾乎是過時的銷售日期。

+0

+1提及設置DEBUG = False因此Django不會記錄所有的SQL查詢。 – Kekoa

+0

哇,什麼Django的心理形象吃多少食物的方式 –

3

以上是否有意義,還是我必須在其他地方尋找問題?這是一個不幸的事故,重要的數據在這個特定的用例中保存了很長時間嗎?

是的,它確實有道理。是的,還有其他問題值得考慮。 Django使用threading.local作爲DatabaseWrapper的基礎(並且一些貢獻使用它來使請求對象可以從未明確通過的地方訪問)。這些全局對象存活請求,並可以保持對對象的引用,直到在線程中處理其他視圖。

有什麼我可以做,以避免這個問題。我已經看到了優化視圖的一些潛力,但這似乎是一個範圍有限的解決方案 - 儘管我不確定我的通用解決方案是什麼。例如,如何手動調用gc.collect()或gc.set_threshold()?

一般建議(可能你知道它,但無論如何):避免循環引用和全局變量(包括threading.local)。當Django設計難以避免它們時,嘗試打破循環並清除全局變量。 gc.get_referrers(obj)可能會幫助您找到需要注意的地方。另一種方式是禁用垃圾回收器並在每次請求後手動調用垃圾收集器,這是最好的地方(這將防止對象移動到下一代)。

我不假設有一種方法可以按代生成gc.get_objects(),例如只從第2代檢索對象,例如?

不幸的是,這對於gc接口是不可能的。但有幾種方法可以去。您可以考慮只返回gc.get_objects()列表的末尾,因爲此列表中的對象按代次排序。您可以通過在調用之間存儲對它們的弱引用(例如,在WeakKeyDictionary之間)來比較該列表和以前調用返回的列表。您可以在自己的C模塊中重寫gc.get_objects()(這很容易,主要是複製粘貼編程!),因爲它們是通過內部生成存儲的,甚至可以通過​​(需要深入瞭解​​)訪問內部結構。

+0

get_objects()正在排序是很好,謝謝你的提示。 – miracle2k