記一次python內存泄露的解決過程


今天在部署實驗室項目時,發現項目在后台運行一個晚上后內存增長了近3g。考慮到目前的數據量較小,真正部署到線上時系統很可能因為OOM而被kill掉,因此進行了長達一天的debug與問題解決。

python 內存泄露

python的垃圾回收采用的是引用計數機制為主,標記-清除和分代收集兩種機制為輔的策略。

在分析內存泄露之前需要先了什么情況會導致內存泄露.具體內容可以參照如下幾篇博客:

檢測內存泄露

接下來檢測下程序中是否出現了內存泄露,pympler工具可以很容易看到內存的使用情況,用法如下

import objgraph
import schedule
from pympler import tracker, muppy, summary


tr = tracker.SummaryTracker()

def dosomething():
    print "memory total"
    all_objects = muppy.get_objects()
    sum1 = summary.summarize(all_objects)
    summary.print_(sum1)

    print "memory difference"
    tr.print_diff()
    # ........ 爬取任務
    pass

schedule.every(1).minutes.do(dosomething)

對我的程序進行分析,可以看到如下的輸出結果,可以看到程序存在緩慢的內存泄露

定位內存泄露位置

接下來要分析是那些對象導致內存泄露的.
相關的工具如下:

  • gc: python 內置模塊, 函數少功能基本, 使用簡單.
  • objgraph: 可以繪制對象引用圖, 對於對象種類較少, 結構比較簡單的程序適用.
  • guppy: 可以用來打印出各種對象各占用多少空間, 如果python進程中有沒有釋放的對象, 造成內存占用升高, 通過guppy可以查看出來. 但僅支持python2.
  • pympler: 可以統計內存里邊各種類型的使用, 獲取對象的大小.

下邊是兩個比較好用的工具,比上述的工具更為方便:

  • pyrasite

    pyrasite 是1個可以直接連上一個正在運行的python程序, 打開一個類似ipython的交互終端來運行命令來檢查程序狀態.
    注: pyrasite使用之前需要在root用戶下運行命令 echo 0 > /proc/sys/kernel/yama/ptrace_scope后才能正常使用
    pyrasite里邊有一個工具叫pyrasite-memory-viewer, 功能和guppy差不多, 不過可以對內存使用統計和對象之間的引用關系進行快照保存, 很易用也很強大.運行命令為 pyrasite-memory-viewer <pid>

  • tracemalloc

    可以直接看到哪個(哪些)對象占用了最大的空間, 這些對象是誰, 調用棧是啥樣的, python3直接內置, python2如果安裝的話需要編譯

最終我定位到問題出現在我調用的hanlp2.0,這個庫用於項目中地名識別,巧的是我在hanlp2的issue里發現了相似的問題pipeline添加自定義function后, 循環使用內存溢出luoy2用了tracemalloc 定位到tokenizer和tagger在pipline方式后循環調用出現了內存泄露的現象。如下是何博士在issue中給出的解釋:

現在任務管理器中Python的內存已經漲到5個G了

fasttext可能本身就要占用幾個G。是增長量達到5個G嗎?

如果是的話是不是就是tf2的bug, 因為據匯報tf1.15 - 沒有上述問題。
可能有關系,有人說 I'm also still facing the same issue with TensorFlow 2.1.0.,也有人說 Still facing this problem in tf-nightly-gpu 2.2.0.dev20200307

不過我沒有在hanlp.com的服務器上觀察到內存泄露的情況。跟python代碼不同,我用的是tensorflow_serving提供服務,從上線到現在沒有重啟過。

已確定是TensorFlow的問題,正在等TF社區回復。

嘗試解決

在本地測試了如下的代碼,並未生效,

del recognizer
del pipline
print(gc.garbage)
gc.collect()

gc.garbage 返回為[], 奇怪的是我雖然嘗試過不使用pipline的方式, 此時使用sys.getrefcount(recognizer),返回的recognizer的引用計數為2,但gc.garbage返回仍為[]。這里關於gc回收的接口參照如下的文檔

由於急着部署項目,只能先考慮備選方案。這里測試對pyhanlp,pytlp,lac進行了測試,感覺百度的lac命名實體識別功能較強。因此將項目中的地名識別模型做了替換。如下是對lac進行的泄露測試,可以看到內存泄露問題解決了。 另外驚喜的是,lac的基礎模型不僅效果而且處理速度極快。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM