[Python] 使用 pympler 偵測記憶體洩露 (memory leak)

[Python] 使用 pympler 偵測記憶體洩露 (memory leak)

最近專案的 Python 程式發現有記憶體洩露 (memory leak) 的問題,

跑越久就吃越多的記憶體…

 

一開始先用 feature flag 的方式,關掉一部分功能,

不過實驗了幾次,似乎並沒有影響,

記憶體增長的曲線斜率看起來是差不多的…

 

上網找了一下,看到了 pympler 這個工具,來試一下吧~

首先安裝 pympler:

pip install pymbler

 

接著,將下面的程式插入有問題的 Python 程式中,

這段程式會使用 pymbler 裡的 muppy 模組,

取得目前記憶體中的物件,統整後依照記憶量用量排序印出來:

from pympler import muppy, summary
all_objects = muppy.get_objects()
sum1 = summary.summarize(all_objects)
summary.print_(sum1)

 

這是執行的結果:

                       types |   # objects |   total size
============================ | =========== | ============
                        dict |        8724 |     13.21 MB
                         str |       43090 |      3.62 MB
                        code |       11095 |      1.35 MB
                        type |        1518 |      1.30 MB
                       tuple |        9389 |    764.92 KB
                        list |        5433 |    601.25 KB
       dpkt.dpkt._MetaPacket |         261 |    230.41 KB
                     weakref |        2244 |    192.84 KB
                         set |         320 |    184.00 KB
                        cell |        3191 |    174.51 KB
           member_descriptor |        2012 |    141.47 KB
          wrapper_descriptor |        1558 |    121.72 KB
           getset_descriptor |        1587 |    111.59 KB
  builtin_function_or_method |        1516 |    106.59 KB
                    property |        1195 |    102.70 KB

 

等程式執行一段時間後,再執行一次,

可以發現 dict 的整體用量從 13.21 MB 增加到 24.18 MB。

顯然 dict 貢獻了非常大的記憶體洩露,

但 dict 太過常見,不容易辨別是哪一部分程式產生的 dict 沒有被釋放掉…

                  types |   # objects |   total size
======================= | =========== | ============
                   dict |       18779 |     24.18 MB
                    str |       56748 |      6.09 MB
                   list |       17373 |      1.53 MB
                   code |       11092 |      1.35 MB
                   type |        1519 |      1.30 MB
                  tuple |       11254 |    891.38 KB
         instancemethod |        4317 |    337.27 KB
  dpkt.dpkt._MetaPacket |         261 |    230.41 KB
                   cell |        3790 |    207.27 KB
                weakref |        2246 |    193.02 KB
                    set |         320 |    184.00 KB
            pycurl.Curl |         327 |    163.50 KB
      member_descriptor |        2014 |    141.61 KB
                    int |        5318 |    124.64 KB
     wrapper_descriptor |        1562 |    122.03 KB

 

再等一陣子後,再次執行,

dict 的整體用量又增加到 66.41 MB,但這次有些不同的東西浮上來了。

注意到 CurlWrapper, CurlResponse, 和 DownloadTask 這些物件變多了,

而且數量蠻接近的,大約都是 4724 或 4714:

                                        types |   # objects |   total size
============================================= | =========== | ============
                                         dict |       57649 |     66.41 MB
                                          str |      104783 |     14.18 MB
                                         list |       63784 |      5.13 MB
                                        tuple |       16935 |      1.66 MB
                                         code |       11222 |      1.37 MB
                                         type |        1555 |      1.33 MB
                               instancemethod |       15382 |      1.17 MB
                                  pycurl.Curl |         930 |    465.00 KB
                                    uuid.UUID |        4728 |    295.50 KB
               common.curlwrapper.CurlWrapper |        4724 |    295.25 KB
              common.curlwrapper.CurlResponse |        4724 |    295.25 KB
                            task.DownloadTask |        4714 |    294.62 KB
                                         cell |        5008 |    273.88 KB
                        dpkt.dpkt._MetaPacket |         261 |    230.41 KB
                                          int |        9443 |    221.32 KB

 

根據對原有程式的理解,每個 DownloadTask 都會產生 CurlWrapper 來下載東西,

所以很有可能是 DownloadTask 因為某種原因未被釋放 (比如說循環參考 cyclic reference),

導致它裡面的 CurlWrapper, CurlResponse 物件,

以及所有相關的物件 (也包含 dict) 都無法被釋放。

 

這時可以去檢查程式裡 DownloadTask 物件在哪邊產生,

又被放到哪裡去,可能就能找出循環參考是哪邊造成的。

 

當然 pymbler 不是只有 summarize() 這個函式可以用,

不過這次用它就算是找到問題囉~^^

 

參考資料:Hunting for Memory Leaks in Python applications

(本頁面已被瀏覽過 1,902 次)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料