定位python內(nèi)存泄漏問題及解決
背景
上周使用我的python web
框架開發(fā)的第二個項目上線了,但是沒運(yùn)行幾天機(jī)器內(nèi)存就報警了,8G內(nèi)存使用了7G,懷疑有內(nèi)存泄漏,這個項目提供的功能就是一堆機(jī)器學(xué)習(xí)模型,對歷史數(shù)據(jù)進(jìn)行訓(xùn)練,挑選出最優(yōu)的5個模型,用作未來數(shù)據(jù)的預(yù)測,所以整個項目有著數(shù)據(jù)量大,運(yùn)行時間長的特點,就是把策略的離線工作搬到了線上。
定位內(nèi)存泄漏
第一步:確定是否有內(nèi)存泄漏
上pympler
檢查是否有內(nèi)存泄漏,程序入口處初始化該工具
from pympler import tracker,summary,muppy memory_tracker = tracker.SummaryTracker()
接口返回處打印內(nèi)存差異,觀察內(nèi)存是否有泄漏
memory_tracker.print_diff() # 本次內(nèi)存和上次內(nèi)存塊的差異
我們用的sanic,所以直接在main.py
文件添加如下代碼:
from pympler import tracker,summary,muppy memory_tracker = tracker.SummaryTracker() @app.middleware('request') async def set_request_id(request): log_id = request.headers.get('log-id') threading.currentThread().logid = log_id gc.collect() memory_tracker.print_diff()
然后我們訪問接口,多觸發(fā)幾次,不用看前兩次,等輸出穩(wěn)定后,如果有內(nèi)存泄漏是如下輸出:
上圖顯示每次都有4類泄漏對象,一共泄漏約60K的內(nèi)存
如果沒有內(nèi)存泄漏,沒有數(shù)據(jù)輸出
第二步:確定內(nèi)心泄漏的代碼塊
我們確定程序有內(nèi)存泄漏后,就想辦法定位到代碼塊,就是我們自己寫的代碼,通過一步一步debug
,注釋,return
,continue
等方式定位到造成泄漏的代碼塊,下面的代碼塊就是遍歷所有模型,然后挨個執(zhí)行訓(xùn)練方法,因為有20多個模型,我不能挨個注釋每次對象來定位,卡在這里了。
第三步:確定泄漏點
上tracemalloc
定位泄漏點,python3.7.3
自帶,在main.py
中添加如下代碼:
tracemalloc.start(25) snapshot = tracemalloc.take_snapshot() @app.middleware('response') async def print_on_response(request, response): global snapshot gc.collect() snapshot1 = tracemalloc.take_snapshot() top_stats = snapshot1.compare_to(snapshot, 'lineno') print("[ Top 10 differences ]") for stat in top_stats[:10]: if stat.size_diff < 0: continue print(stat) snapshot = tracemalloc.take_snapshot()
繼續(xù)訪問接口,多訪問幾次,輸出如下,直接定位到具體泄漏的代碼位置
圖中所有的泄漏點都定位到pandas
庫,但是我用這些文件搜索內(nèi)存泄漏,都沒有搜到相關(guān)內(nèi)存泄漏的問題,所以得尋找誰調(diào)用這些地方,以
/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py:859
為例,我們要找到我們寫的代碼哪里調(diào)用觸發(fā)泄漏點
第四步:打印調(diào)用鏈
看了tracemalloc
文檔也沒找到打印調(diào)用鏈的方法,后來靈機(jī)一動直接在這個文件加了下面代碼:
raise Exception("doctorq")
然后在接口里catch異常,添加logging.exception(e)
,然后觸發(fā)接口,打印堆棧信息:
ERROR:root:doctorq
Traceback (most recent call last):
File "/home/doctorq/python-dev/scscore/src/forecasting/forecast.py", line 83, in update_method
n_fraction=n_fraction)
File "/home/doctorq/python-dev/scscore/src/forecasting/trainer.py", line 113, in training
n_fraction=n_fraction)
File "/home/doctorq/python-dev/scscore/src/forecasting/trainer.py", line 205, in train_machine_learning_model
is_train=True).dropna()
File "/home/doctorq/python-dev/scscore/src/feature_engineering/features.py", line 34, in get_feature
history_same_periods=history_same_periods, zero_replace=zero_replace)
File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 65, in get_feature
store_and_sku=store_and_sku)
File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 85, in get_rolling_feature
rolling_result = self.get_rolling_result(window, rolling_obj, rolling_types)
File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 169, in get_rolling_result
rolling_result = self.rolling__(rolling_obj, rolling_type)
File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 190, in rolling__
return rolling_obj.min()
File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 1723, in min
return super(Rolling, self).min(*args, **kwargs)
File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 1069, in min
return self._apply('roll_min', 'min', **kwargs)
File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 879, in _apply
result = np.apply_along_axis(calc, self.axis, values)
File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/numpy/lib/shape_base.py", line 380, in apply_along_axis
res = asanyarray(func1d(inarr_view[ind0], *args, **kwargs))
File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 875, in calc
closed=self.closed)
File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 858, in func
raise Exception("doctorq")
Exception: doctorq
定位到我們代碼觸發(fā)點如下:
調(diào)用的就是pandas
的Rolling
的一系列方法,然后搜索該方法是否有泄漏問題
第一個鏈接鏈接就是說這些方法(rolling.min/max
)有泄漏,pandas rolling max leak memory,
具體因為啥泄漏的,也沒時間細(xì)究,反正issue
里說回退到0.23.4
是沒問題的,那么就回退試試:
pipenv install pandas==0.23.4
然后我們再用pympler
定位有沒有內(nèi)存泄漏,pandas
內(nèi)存泄漏的問題是修復(fù),剩下來就省memoryview
的小泄漏了,明天繼續(xù)
總結(jié)
定位的過程略耗時,不過經(jīng)過這么一折騰,也算是有經(jīng)驗了,各種工具一陣堆,泄漏問題確定-定位代碼塊-定位泄漏點-搜索已知泄漏點-解決掉。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python模仿POST提交HTTP數(shù)據(jù)及使用Cookie值的方法
這篇文章主要介紹了Python模仿POST提交HTTP數(shù)據(jù)及使用Cookie值的方法,通過兩種不同的實現(xiàn)方法較為詳細(xì)的講述了HTTP數(shù)據(jù)通信及cookie的具體用法,需要的朋友可以參考下2014-11-11舉例講解Python程序與系統(tǒng)shell交互的方式
這篇文章主要介紹了Python程序與系統(tǒng)shell交互的方式,舉了一個非常簡單的hello world的例子,需要的朋友可以參考下2015-04-04python文字和unicode/ascll相互轉(zhuǎn)換函數(shù)及簡單加密解密實現(xiàn)代碼
這篇文章主要介紹了python文字和unicode/ascll相互轉(zhuǎn)換函數(shù)及簡單加密解密實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-08-08Python獲取Linux系統(tǒng)下的本機(jī)IP地址代碼分享
這篇文章主要介紹了Python獲取Linux系統(tǒng)下的本機(jī)IP地址代碼分享,本文直接給出實現(xiàn)代碼,可以獲取到eth0等網(wǎng)卡的IP地址,需要的朋友可以參考下2014-11-11Python reduce()函數(shù)的用法小結(jié)
reduce()函數(shù)即為化簡函數(shù),它的執(zhí)行過程為:每一次迭代,都將上一次的迭代結(jié)果,需要的朋友可以參考下2017-11-11