加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

一种内存池实现方法,用标准C++实现,可移植

(2013-07-04 16:19:04)
标签:

内存池

c

分类: 软件开发

引言

最近想实现一个消息队列的程序。消息的基本思想和快递类似: 实际就是数据结构的打包和发送: 申请一块内存,存入需要的数据,把地址告诉接收方让接收方去拿。难免会对内存频繁的请求释放,导致内存碎片化。 所以就想到用内存池来管理内存的申请释放。发送方在内存池中申请内存, 接收方到指定地址取数据,用完之后放回内存池。

 

网上搜了一下,内存池的实现方式五花八门。其中比较典型的是这篇文章中提到的方法:

http://wenku.baidu.com/view/a46d5e6348d7c1c708a1450a.html

以及

http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html?ca=drs-cn

 

不过此文中的内存池只能申请单个内存单元。对于申请较大的内存,除非修改内存单元的大小。 但那样会造成很大浪费。 于是就在这个内存池的基础上进行了修改


源码地址:

http://www.oschina.net/code/snippet_133867_22703


 

基本架构

原版内存池结构:

 

http://s4/mw690/4868f986ge0adc3901663&690

 

 

改进版内存池结构:


http://s10/mw690/4868f986ge0adc4fe9a79&690


 

名词

首先确定一下名词:

1) Memorypool叫内存池

2) Memorybolck叫内存块。 

3) aData里面可以分配的的每一小段叫内存单元

4) aData里面的连续空闲内存单元叫做空闲内存单元片(简称空闲片),他是由多个内存单元组成的。

 

改进方法

1) 与原版所不同的在于对于本改进版的memoryblock 是可以取用内存单元片的,而不仅仅是一块内存单元。

 

2) 对于空闲内存片: 利用内存片的开头四个字节(原例子是两个字节,这里做了扩展)存储特定数据 ,前两个字节用于存储下一个空闲片的偏移, 其后两个字节存储本空闲片的长度。 

3) 分配内存的时候,首先根据申请长度计算出所需内存单元的数量。然后逐块查找有无空闲内存片。如果没有,则开辟新的内存块供分配。待分配内存片的前两个字节存储分配的内存单元个数。

4) 内存归还的时候,首先找到归还地址所对应的内存块,再找到此地址对应内存片的起始位置, 如果该内存片前后有空闲片,则将前后的空闲片合并。

5) Mempool构造时不申请内存块。 内存不够用的时候申请内存块。内存块个数有上限, 默认200个。内存块完全空闲的时候不释放,最后析构的时候统一释放。 

 

内存片合并示意图

http://s4/mw690/4868f986ge0adc4eec803&690

 

数据结构

对于MemoryBlock结构,有如下参数:

nFree 表示本内存块中剩余内存单元的个数

nFirst 指向第一个空闲片的偏移

nSize 本内存块中, 内存单元的个数

pNext 存储下一个内存块地址

aData 存储内存片位置

 

对于memorypool结构,有如下参数:

   MemoryBlock*    m_pBlock;      //起始内存块地址

   SHORT          m_nUnitSize;   //每一个内存单元的大小,默认64字节

   SHORT          m_nInitUnitCount;   //初始内存块中内存单元个数

   SHORT          m_nGrowUnitCount;   //新增内存块中内存单元个数

   SHORT          m_BlockCount;       //内存块个数

   SHORT          m_nMaxBlockCount;    //内存块最大个数,默认200

测试结果:

用以下程序测试:

//测试内存池速度

    {

       list<void *>  used_list;

       printf("00000\n");

       for(int i=0; i < 1000000; i++){

           UINT rand_num = rand();

           UINT to_do = rand_num%2;

           void *data;

           if(to_do == 0){ //申请内存

               LOGE("Alloc memory!!!");

               data = mem_pool.Alloc(rand_num%60);

               if(data)

                   used_list.push_back(data);

           }else{//释放内存

               if(used_list.empty())

                   continue;

               LOGE("give back memory!!!");

               data = used_list.front();

               used_list.pop_front();

               mem_pool.Free(data);

           }

       }


       printf("11111\n");

       while(! used_list.empty()){

           void *data = used_list.front();

           used_list.pop_front();

           mem_pool.Free(data);

       }

       printf("over!!!\n\n\n");

       fflush(stdout);


   }

//测试系统new delete的速度

   {

       list<BYTE *>  used_list;

       printf("22222\n");

       for(int i=0; i < 1000000; i++){

           UINT rand_num = rand();

           UINT to_do = rand_num%2;

           BYTE *data;

           if(to_do == 0){ //申请内存

               LOGE("Alloc memory!!!");

               data = new BYTE[rand_num%60];

               if(data)

                   used_list.push_back(data);

           }else{//释放内存

               if(used_list.empty())

                   continue;

               LOGE("give back memory!!!");

               data = (BYTE *)used_list.front();

               used_list.pop_front();

               delete [] data;

           }

       }


       printf("33333\n");

       while(! used_list.empty()){

           BYTE *data = used_list.front();

           used_list.pop_front();

           delete [] data;

       }

       printf("over!!!\n");

   }

 

同样随机存取100万次,每次申请的大小小于60字节。  内存池速度大约0.8s, 略慢于系统new delete的速度。 但是1000万次则会慢于new delete。 估计是由于链表查找速度比较慢。

 

结论:

内存池能够有效避免内存碎片化,控制内存泄露,至少使内存泄露只发生在池中,避免影响整个系统,且便于跟踪内存使用情况。这是其很大的优点,所以对于长期运行的系统来说,内存池的好处是显而易见的 。但是要在速度上优于newdelete,恐怕不是那么容易,要深入的研究算法。 

本文的改进版内存池,可以分配多块内存单元。较原版通用性更好。不过相应的性能会有所下降。

由于通常情况下,单台机器短时间内很少有百万次内存申请释放。所以还是可以很好地应用到程序中。

 

 

 后续可改进的地方:

对比tcmalloc的实现,这个内存池显然差的远。可以发现一个显著的可以改进的地方:

内存块里面的内存单元大小可以不同。 可以构造一些内存单元小的块,用于小内存申请。 一些内存单元大的块用于大内存申请。 如此区分一下可以减少内存合并的情况,提高速度





 

 

 

 

 

 

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有