12 8月 2013

[Python] 使用objgraph找出memory leak(circular reference)

在Python的世界裡,所有的東西都是Object,而控制Object的life cycle靠的就是reference的機制,一個簡單的assignement (=等號),或是function call就會讓Object的reference count自動加1。
而當Object的reference count減至0的時候,Python的GC會找時間把這些Object清除掉。

對Python而言,memory leak產生的可能性有兩種
  • Circular reference
  • Referece在global的變數或是module上


其實有些tool可以找到這種reference的關係,這邊介紹的是 objgraph - 官網,視覺化的Reference結果圖,讓人一目瞭然。

可以在下載後安裝
pip install objgraph
或是直接將壓縮檔內的objgraph.py解壓放在執行檔旁,因為objgraph就這麼一個檔案!

不過,為了將objgraph解析後的狀態轉成圖檔,還需要安裝另一個tool - graphviz,這個是跟python無關的tool,只是objgraph輸出一個config檔,透過subprocess執行graphviz去讀取這個config檔,然後輸出最後關係圖的圖檔。接著可以透過關係圖檔找到應該解除reference link的地方,再一一破解,應該就可以解開memory leak了。

以下示範較常用的show_backrefs()
還有其他的API可直接參照objgraph.py裡

Example: Value object reference 在 Container 裡

class Value(object):
    def __init__(self, v):
        object.__init__(self)
        self.v = v
    def get(self):
        return self.v
    def set(self, v):
        self.v = v
        
class Container(object):
    def __init__(self):
        object.__init__(self)
        self.children = []
    def get(self, idx):
        return self.children[idx]
    def pop(self):
        c = self.children.pop()
        return c
    def push(self, c):
        return self.children.append(c)
        
if __name__== '__main__': 
    c1 = Container()
    c1.push(Value(100))
    
    import objgraph
    objgraph.show_backrefs([c.get(0)], filename='test.png')
輸出結果圖:

Example 2:  2個Container同時reference在同一個Value object

if __name__== '__main__': 
    c1 = Container()
    c1.push(Value(100))
    
    c2 = Container()
    c2.push(c1.get(0))

    import objgraph
    objgraph.show_backrefs([c.get(0)], filename='test.png')
輸出結果圖:


Example 3: circular reference

if __name__== '__main__': 
    c1 = Container()
    v = Value(c1)
    c1.push(v) #circular reference
    
    import objgraph
    objgraph.show_backrefs([c1.get(0)], max_depth=5, filename='test.png')
輸出結果圖:

Reference:

沒有留言: