今天在群里看见有人在讨论内存池的问题,呵呵,想到自己也写过一些内存池,在这里抛砖引玉。 其实内存池的作用大家也知道,一般是解决大量的new和delete频繁操作引起的内存碎片,效率是一方面,另外长时间后的安全性也是一个问题。曾经看过《C++应用程序性能优化》里面的内存池结构,也看过ACE自己的自增式内存池结构,感觉每个都有自己的优点。但是大体思路都是一致的,那就是一次new出一大块内存,然后按照2的幂分配内存块。当申请的内存不够的时候,就会再次分配一个新的内存块,并按照指定的方式切割成小块。如此实现内存的申请与释放。 图示如下: 2个字节空间的链表 内存块1->内存块2->内存块3...... 4个字节空间的链表 内存块1->内存块2->内存块3...... 8个字节空间的链表 内存块1->内存块2->内存块3...... 16个字节的空间链表 内存块1->内存块2->内存块3...... ....... 当用户申请内存的时候,根据用户申请的大小块去匹配最接近的2的N次幂的链表。然后从中找到标记为"自由"的内存块,并将指针返回给用户使用。如果链表用完了,也就是所有的块都被占用了,会根据不同的策略生成指定新的连续内存块,提供给用户使用。其实这个架构,大概就是ace的内存池所做的事情。其实大部分内存池不同也就在于如何在重新申请新大内存块的策略不同,原理都是一样的。 以前读过网易云风写过的一本书(《我的编程感悟》),关于游戏中内存的申请与释放的内存池架构,并给出了代码,与《C++应用程序性能优化》不同的是,云风针对不同内存块使用了模板,说真的,从写C++到现在,我个人比较讨厌模板的过度使用,虽然云风说模板的应用会给游戏破解者造成很大麻烦,但是我个人认为这样的做法一样会使阅读你代码的人感到晦涩难懂,尤其是一些高阶模板的应用,会让一些C++初学者感觉像看天书一般,我个人而言也很讨厌看这样的代码。 内存池对于那些需要长时间稳定运行的服务程序尤其重要,几乎所有的24*7运行的系统中都多少包含各种内存池缓冲。下面就说说我写的内存池架构。 其实和上面描述的都差不多,只不过,面向我曾经做过的应用而言,如果一个程序中,有大量的等量内存块的申请(等量内存块指的是,内存块申请和释放大小固定),倒是完全可以再简化一下以上的设计。针对初学者,我希望我能写出一篇比较简单的功能,随时随地拿着走,在不改变任何已有代码的请前提下,将内存池替换到你的系统中去。我写的内存池很简单,但是用到了一些微软曾经做过的手段,呵呵,来切实提高内存池的效率。 从初学者的角度,先看看如果要做一个内存池,需要从哪些方面去想。 (1)我的内存池需要能够在不改动原有代码的情况下,接替new和delete。 (2)我的内存池需要在我需要的时候,给我展示内存池内数据使用的情况(查找内存泄露) (3)我的内存池需要快速的实现内存的回收和再利用。 (4)我的内存池需要对多线程内存申请提供支持。 恩,内存池设计遇到的难点也是存在的。比如: (1)我如何控制我的内存池有序的增长 (2)在给定一个指针,遍历内存数据的时候,如何做到最为高效。 (3)如何对内存泄露做监控? 其实网上这方面的资料实在是太多了,也有很多很好的例子。我只是贴出我的代码,给大家提供一些参考。
在这里我使用了一个小技巧,当用户申请内存的时候,我返回给用户内存地址不是我内存块一开始的地址,而是向后偏移了12个字节(当然,new的时候会是 实际大小 + 12个字节),这多出来的12个字节,就是内存头,记录用户申请内存时这块内存的一些参数,因为当用户delete的时候,如果我有一个无比庞大的内存列表,我一个个去遍历这样的效率我是无法接受的。所以我在这里存储了12个字节的内存快大小,这样做我能很快的定位这个内存地址在我的哪一个链表下,在什么位置,这样做完全可以在不做循环的接触上,在海量的内存里迅速定位自己的位置。并完成内存块使用和非使用之间的数据转移。其实,对于用户而言,偏移的这个内存位置它完全不用知道,微软当年就是这么干的,只不过内存头记录的东西比我还多。当释放内存的时候,找到内存头只需要向前获取4个字节的指针位置即可。其实,如果你喜欢,完全可以继续扩展增加这四个字节,比如记录何时申请的等等。。。这一招我觉得真的很有效果。
呵呵,其实,我的内存池很简单,最复杂的就是对内存链表的位移操作,其实这部分想清楚就行了,相对于云风和《C++性能优化》的内存池不同,有了一些自己的想法。对于内存池,稳定性是很不错的,优秀的搜索算法更能帮助我们提升自己的效率。站在他们的肩膀上,进一步优化了一下内存池的架构。 当然,你们要使用它很简单。只需要重载new和delete。 附件是我这个内存池的代码原本,在windows下和linux下都测试通过,并且现在还运行在一些项目中,比较稳定。希望能够帮助大家。 在这里,我加了一点数据锁,为了保持对内存申请和释放的线程安全,这部分见仁见智。 另外,声明一点,我的内存池不适于那种dll里面new,然后在程序里delete的操作,因为dll和exe有可能有不同的副本。我曾经把我的内存池移植到ACE中,后来发现在ConnectHandle的时候有这样的现象,只好使用ACE的内存池。这点要注意,当然,大家有对这个问题的好的解决方法,请告诉我,谢谢。 [ 本帖最后由 freeeyes 于 2010-4-6 12:29 编辑 ] |
-
MemoryPools.rar (4.42 KB)
内存池源代码