編程(一)C++內存泄露排查方法

背景描述

最近工作的內容是搭建推薦系統的debug平臺,即將一次推薦請求過程中比較關鍵的推薦信息都以可視化的形式展現出來。舉個例子,一個推薦系統處理推薦請求大致分爲如下步驟(有點老老生常談):

  1. 涉及到很多路召回(recaller),每路召回都會產出很多個候選資源(candidates);
  2. 這些候選資源會經過若干個過濾器(filter),將那些近乎無效的資源(重複、年代太久遠)過濾掉;
  3. 粗略排序+精細排序(ranker),得到最終展現在用戶面前的若干資源。

這只是最簡單的過程,因爲上述過程中的每個環節都有可能存在a/b test環節,因而你並不知道某一個資源從產生到展示在用戶面前經歷了哪些步驟,如果哪天某個老大報了一個case,說我一個年過半百的成功人士爲什麼要給我推送海綿寶寶?然後我們這羣小弟就必須從上倒下捋一遍整個推薦過程,拉線上日誌,費心費力地找了半天,才發現有一路召回可能存在問題,之後再上線修復這個case,這樣排查case的方式費心費力。

從上面的背景可以看出搭建推薦系統debug平臺的重要性,因而這個任務就落在了我這個新人頭上。經過將近一個月的組內成員各方同步+編寫代碼之後,終於可以提測。測試環節QA提出我的代碼有內存泄露的情況,1分鐘服務器的內存增長1個G,我知道我這次的項目中會頻繁操作內存,因爲我要把整個推薦鏈路的信息都保存下來,但一分鐘增長1個G,確實有點恐怖,確實有點嚇人。

查閱資料

valgrind

QA大佬那邊有一個排查內存泄露的工具,但是最近不能用,因爲整個大組的C++的gcc是8.2版本的,公司內部的valgrind只支持gcc4.8.2,因而多番嘗試之後放棄。

這裏順便附上valgrind的使用說明,希望能夠對大家有所幫助。

asan

接下來是asan,這個工具在公司內部用的比較多,而且是整個大組一個架構大佬推薦的,操作比較簡單,而且運行速度也比valgrind快,跑起來發現有個地方內存泄露,但是和我的代碼無關,經過多番周折之後發現這段代碼只是new和free沒有匹配,寫這段代碼的大佬告訴我new和free雖然不匹配,但是後面代碼"隱式調用"了free,因而依然沒問題。跳過了new和free的不匹配選項,跑了一會發現根本沒有內存泄露異常的報警,依然費解…

這裏還是順便附上asan的使用說明,希望能夠幫到大家。

排查方法

無奈之下,我只能自己手動排查錯誤了,思考了很久,總結了一套方法:

  1. 首先要排查的是線上Base版本代碼是否有問題,有的人可能會覺得好奇,爲什麼線上Base版本會出現問題,這是我的老大傳授給我的,要對一切事情報以懷疑的態度,沒有人規定線上版本不會出現問題,所有的錯誤如果都能在測試環節發現,就不會有所謂的線上事故了,跑了一遍,發現線上Base版本的代碼在壓測過程中內存漲到一定程度之後就穩定不變了;
  2. 將自己所有的代碼都註釋掉,然後逐個添加自己代碼的核心功能,壓測時看下內存變化,經過差不多一天的嘗試之後,當添加記錄資源信息功能時內存增長過快,問題找到,這裏分享下我在排查過程中腦圖的記錄

經過排查代碼和多方查證,發現protobuf會佔用機器很多內存,特別是使用mutable和Clear的時候一定要小心,因爲mutable操作不當會多申請內存,Clear過的數據結構並沒有釋放,而且重複使用,因而想出來的方案是降低使用protobuf的頻次,這樣能夠最大化地降低內存增長的風險,如下是我查閱protobuf對內存增長相關的文檔,希望能幫助到大家:

  1. Protobuf使用不當導致的程序內存上漲問題
  2. Protobuf中ShutdownProtobufLibrary函數的使用

總結

  1. 雲端的內存泄露或者內存增長過快的問題一版不好排查,因爲涉及到的模塊很多,所以遇到這種問題時必須要冷靜下來,在沒有外界工具的前提下一定要冷靜地思考到底哪裏出現問題。
  2. 個人代碼相關問題的解決只能靠自己,因爲別人的工作都很忙,沒有人能夠平心靜氣地幫你捋一遍你的代碼邏輯以及這個過程中出現的代碼漏洞,這個責任只能你一個人扛。
  3. 抱着懷疑一切的態度工作,不放過任何一個可能出錯的細節。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章