简单的Memory leak跟踪(四)参考代码、组织和几个问题的讨论

参考代码

摘录了相关的代码,在小生的CSDN资源站里,0分下载,链接如下。
http://download.csdn.net/detail/noslopforever/4568056
转载请使用本资源连接。

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,相信我,这只是噩梦的开始……吐舌头你永远无法知道用户会怎么使用你提供的库和接口……所以唯一能做的,只能是让他们无从选择。


跨模块是一个相当让人绞尽脑汁的特性,如果您自己完全可控的项目,建议还是不要在这个路上走得太远。毕竟,适应的才是最好的,您说呢?


你可能感兴趣的:(优化,list,File,delete,include,leak)