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

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

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

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

 

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

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

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

 

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

首先安裝 pympler:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
pip install pymbler
pip install pymbler
pip install pymbler

 

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

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

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
from pympler import muppy, summary
all_objects = muppy.get_objects()
sum1 = summary.summarize(all_objects)
summary.print_(sum1)
from pympler import muppy, summary all_objects = muppy.get_objects() sum1 = summary.summarize(all_objects) summary.print_(sum1)
from pympler import muppy, summary
all_objects = muppy.get_objects()
sum1 = summary.summarize(all_objects)
summary.print_(sum1)

 

這是執行的結果:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
                       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 沒有被釋放掉…

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
                  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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
                                        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()
summarize() 這個函式可以用,

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

 

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

(本頁面已被瀏覽過 2,026 次)

發佈留言

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

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