簡單的Memory leak跟蹤(四)參考代碼、組織和幾個問題的討論

參考代碼

摘錄了相關的代碼,在小生的CSDN資源站裏,0分下載,鏈接如下。
轉載請使用本資源連接。

Tracer的變種

Tracer稍加變化,就可以記錄更豐富的信息。例如,首先不用hash了,直接使用一個list來記錄,free時不再從hash裏刪除了,list只會越變越大,然後記錄例如分配時間、銷燬時間、分配大小、線程等等等等信息。這樣子就可以將整個應用程序的內存處理給監控下來。
U3就使用了Dbghelp trace來記錄當前應用程序的所有分配,一段時間內的,甚至是整個應用程序生命期的。這樣做可以提供更多關於內存分配的信息,知道哪些時候、分配內存的調用過於集中,哪些時候,銷燬內存的調用過於集中,還是分配和銷燬都是平穩執行和發展的。
但是每次增加新的信息,都會讓Tracer變得更慢。

File Line Tracer 和 Dbghelp Tracer 各自的優劣

首先,從性能上,File Line Tracer 所需的信息均來自編譯期,運行時除了程序棧和hash之外不存在新的調用開銷,而Dbghelp的信息則來自於運行時,開銷自然比File Ln Tracer大得多。

然後,File Line Tracer需要define new,這會引入一些小麻煩,Dbghelp tracer則不需要這個東西。define new後面繼續展開。

再然後,File Line Tracer只知道”分配內存的當前語句所在的文件和行號“,但DbgHelp還可以給出”分配內存的當前Call stack,更利於快速定位到錯誤的分配“。

File Line Tracer 的 define new 和因此帶來的問題

define new最大的問題在於需要確保在new之前調用#define new DEBUG_NEW宏。

頭文件包含關係比較亂的時候,這一點就比較難受。如果有預編譯頭,相對好辦點,只要在預編譯頭的第一行加入這句話即可。但沒有預編譯頭的時候,這就需要用戶自己維護其正確性了。

否則,萬一有.h裏new,.cpp裏delete這樣的情況(或者相反),而define new又發生在這個.h之後,就極易發生誤判的情況,某個new明明被刪除了,但是沒有記錄下來,於是誤報了內存泄露,或者某次刪除發現刪的不是相應的new……

頭文件順序真是C++永遠的痛,傷不起啊傷不起……


Tracer優化

Cookie優化

無論哪個tracer,都增加了一些代碼的開銷。

如果內存分配器也是自己寫的,這裏就方便一點,內存分配的時候可以多在前分出一些小Cookie,在這些小Cookie裏面記錄所需的信息。然後需要這些信息的時候,只需要向前尋址若干字節,獲取出Cookie,這樣的性能是最高的,但會引入兩個問題:

一,全套內存分配要自己做,dlmalloc、tlmalloc等成熟的第三方分配用不了了,這也是爲什麼我的例子代碼裏使用了獨立的Tracer的原因。

二,要確認當前訪問的內存是由本分配器分配出來的,一旦new與delete不配對,這種問題就會如雨後春筍般出現。一個不太好但是大部分情況下適用的方案是Cookie裏記錄一個魔數,每次訪問時先判斷魔數能不能對上,畢竟大部分場合下,內存裏的數據正好對上魔數的可能性極低。但這種問題還是防不勝防。當你提供的並非全套解決方案,而是隻是一個小模塊,且會被其它人代碼級而非二進制級引用時,這個問題就可能會變得愈發突出。


多進程優化

如果想避免hash的開銷,還有一個辦法就是用另外一個線程,將每次Trace的信息發送到其它進程去處理。由於消息可以入隊,而處理可以在程序空閒時和退出時再處理,所以對程序運行時的開銷影響就會減少。具體的方法,可以是寫文件,可以是用TCP發到其它服務器,可以是寫入共享內存,那就完全取決於您自己的意願和實測結果了。

所佔內存優化

如果把Trace按”變種“章節所說的,改成全截獲,永不銷燬,那麼接下來要面臨的問題就是,一旦分配多起來,這內存佔用就呼呼地往上漲了。

這時也有方案,就是把Tracer的改成不在本進程處理,而是將消息通過TCP連接發給其它應用程序去截獲和處理。但這樣的話,對DbgHelper來說,發送的信息就必須得包括Call stack的全文信息。否則另一個應用程序如果得到的只是Stack標記,要反解出來就要麻煩很多很多。好在發送線程可以做在另一個線程立,而每個Call stack全文信息獲取一次以後可以緩存下來,Call stack全文信息的數量相對於分配數總歸是少的,不是嗎?


Tracer的跨模塊調用

Tracer跨模塊後,就會變成比較頭疼的問題。

如果所有的模塊全都是您自己維護的,那麼您倒是可以保證您的Tracer公平地在每個模塊裏使用,不會出現問題。

但是如果您製作的是一個可發佈的、相對精煉且功能相對單一的可分發程序包時,Tracer就要萬分小心了。

首先,用戶未必希望開啓此模塊的Tracer功能,這就需要提供專門的MEMLEAK DEBUG版本。

然後,如果用戶使用了Tracer,就要確保由Tracer new出來的內存,也要由Tracer監督其delete,這裏就需要守住”本模塊new本模塊刪“的原則,但是原則嘛,自然是說着容易做着難了。你中槍了木有?

再然後,如果用戶不想使用Tracer,或者不想讓用戶使用Tracer,就需要在分發版本的include文件中排除掉Tracer,而.h裏相應使用的就需要用宏屏蔽。這個應該大家都是這麼做的,權當廢話好了。


最後,如果用戶想用你的Tracer,相信我,這只是噩夢的開始……吐舌頭你永遠無法知道用戶會怎麼使用你提供的庫和接口……所以唯一能做的,只能是讓他們無從選擇。


跨模塊是一個相當讓人絞盡腦汁的特性,如果您自己完全可控的項目,建議還是不要在這個路上走得太遠。畢竟,適應的纔是最好的,您說呢?


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章