[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