当前位置: 首页 >> 程序设计 >> 基于C语言的内存池设计与实现
 

基于C语言的内存池设计与实现

作者:      来源:http://blog.csdn.net/ugg     发表时间:2007-03-28     浏览次数:      字号:    


5:内存池的分配原理

在内存池的设计中,有两个重要的操作过程1:chunk_alloc,申请大块内存,2:refill回填操作,内存池初始化化时并不是为索引表中的每一项都创建空闲分配链表,这个过程会推迟到,只有用户提取请求时才会创建这样的分配链表。详细参考如下代码(在sgi中stl_alloc.h文件中你也可以看到这两个函数),主要步骤在注释中已经说明。

/**
* @bri: 申请大块内存,并返回size*(*nobjs)大小的内存块
* @param: size,round_up对齐后的大小,nobjs
* @return: 返回指向第一个对象内存指针
*/
static char* chunk_alloc(size_t size, int *nobjs)
{
     /**< 返回指针 */
     char* __result;
     /**< 申请内存块大小 */
     size_t __total_bytes = size *(*nobjs);
     /**< 当前内存可用空间 */
     size_t __bytes_left = _end_free - _start_free;

     /**< 内存池中还有大片可用内存 */
     if (__bytes_left >= __total_bytes)
     {
         __result = _start_free;
         _start_free += __total_bytes;
         return (__result);
     }
     /**< 至少还有一个对象大小的内存空间 */
     else if (__bytes_left >= size)
     {
         *nobjs = (int)(__bytes_left/size);
         __total_bytes = size * (*nobjs);
         __result = _start_free;
         _start_free += __total_bytes;
         return (__result);
     }
     /**< 内存池中没有任何空间 */
     else
     {
         /**< 重新申请内存池的大小 */
         size_t __bytes_to_get = 2 * __total_bytes + round_up(_heap_size >> 4);
         /**< 把内存中剩余的空间添加到freelist中 */
         if(__bytes_left > 0)
         {
              _obj *VOLATILE* __my_free_list = 
                   _free_list + freelist_index(__bytes_left);
              ((_obj*)_start_free)->free_list_link =
*__my_free_list;
              *__my_free_list = (_obj*)_start_free;
         }
         // 申请新的大块空间
         _start_free = (char*)malloc(__bytes_to_get);
         /*=======================================================================*/
         memset(_start_free,0,__bytes_to_get);
         /*=======================================================================*/
         // 系统内存已经无可用内存,那么从内存池中压缩内存
         if(0 == _start_free)
         {
              size_t __i;
              _obj *VOLATILE* __my_free_list;
              _obj *__p;
              /**< 从freelist中逐项检查可用空间(此时只收集比size对象大的内存空间) */
              for (__i = size; __i <= (size_t)__MAX_BYTES; __i += __ALIGN)
              {
                   __my_free_list = _free_list + freelist_index(__i);
                   __p = *__my_free_list;
                   /**< 找到空闲块 */
                   if (__p != 0)
                   {
                       *__my_free_list = __p->free_list_link;
                       _start_free = (char*)__p;
                       _end_free = _start_free + __i;
                       return (chunk_alloc(size,nobjs));
                   }
              }
              _end_free = 0;
              /**< 再次申请内存,可能触发一个异常 */
              _start_free = (char*)malloc(__bytes_to_get);
         }
         /**< 记录当前内存池的容量 */
         _heap_size += __bytes_to_get;
         _end_free = _start_free + __bytes_to_get;
         return (chunk_alloc(size,nobjs));
     }
}

/*=======================================================================*/
/**
 * @bri: 填充freelist的连接,默认填充20个
 * @param: __n,填充对象的大小,8字节对齐后的value
 * @return: 空闲
 */
static void* refill(size_t n)
{
     int __nobjs = 20;
     char* __chunk = (char*)chunk_alloc(n, &__nobjs);
     _obj *VOLATILE* __my_free_list;
     _obj *VOLATILE* __my_free_list1;
     _obj * __result;
     _obj * __current_obj;
     _obj * __next_obj;
     int __i;
     // 如果内存池中仅有一个对象
     if (1 == __nobjs) 
         return(__chunk);
     __my_free_list = _free_list + freelist_index(n);
     /* Build free list in chunk */
     __result = (_obj*)__chunk;
     *__my_free_list = __next_obj = (_obj*)(__chunk + n);
     __my_free_list1 = _free_list + freelist_index(n);
     for (__i = 1;; ++__i)
     {
         __current_obj = __next_obj;
         __next_obj = (_obj*)((char*)__next_obj+n);
         if(__nobjs - 1 == __i)
         {
              __current_obj->free_list_link = 0;
              break;
         }else{
              __current_obj->free_list_link = __next_obj;
         }
     }
     return(__result);
}

经过上面操作后,内存池可能会成为如下的一种状态。从图上我们可以看到,已经构建了8,24,88,128字节的空闲分配链表,而其他没有分配空闲分配链表的他们的指针都指向NULL。我们通过判断索引表中的指针是否为NULL,知道是否已经构建空闲分配表或者空闲分配表是否用完,如果此处指针为NULL,我们调用refill函数,重新申请20个这样大小的内存空间,并把他们连接起来。在refill函数内,我们要查看大内存中是否有可用内存,如果有,并且大小合适,就返回给refill函数。


图4

 

    6:线程安全
    采用互斥体,保证线程安全。

内存池测试

    内存池的测试主要分两部分测试1:单线程下malloc与mempool的分配速度对比2:多线程下malloc和mempool的分配速度对比,我们分为4,10,16个线程进行测试了。
    测试环境:操作系统:windows2003+sp1,VC7.1+sp1,硬件环境:intel(R) Celeron(R) CPU 2.53GHz,512M物理内存。

    申请内存空间设定如下
#define ALLOCNUMBER0 4
#define ALLOCNUMBER1 7
#define ALLOCNUMBER2 23
#define ALLOCNUMBER3 56
#define ALLOCNUMBER4 10
#define ALLOCNUMBER5 60
#define ALLOCNUMBER6 5
#define ALLOCNUMBER7 80
#define ALLOCNUMBER8 9
#define ALLOCNUMBER9 100

    Malloc方式和mempool方式均使用如上数据进行内存空间的申请和释放。申请过程,每次循环申请释放上述数据20次
    我们对malloc和mempool,分别进行了如下申请次数的测试(单位为万)

2

10

20

30

40

50

80

100

150

200

malloc和mempool在单线程,多线程,release,debug版的各种测试数据,形成如下的统计图


图5

可以看到mempool无论在多线程还是在单线程情况下,mempool的速度都优于malloc方式的直接分配。

    Malloc方式debug模式下,在不同的线程下,运行时间如下,通过图片可知,malloc方式,在debug模式下,申请空间的速度和多线程的关系不大。多线程方式,要略快于单线程的运行实现。


图6

    Malloc方式release模式测试结果如下。


图7

多线程的优势,逐渐体现出来。当执行200w次申请和释放时,多线程要比单线程快1500ms左右,而4,10,16个线程之间的差别并不是特别大。不过整体感觉4个线程的运行时间要稍微高于10,16个线程的情况下,意味着进程中线程越多用在线程切换上的时间就越多。

下面是mempool在debug测试结果


图8

    下面是mempool在release模式下的测试结果


图9

    以上所有统计图中所用到的数据,是我们测试三次后平均值。

通过上面的测试,可以知道mempool的性能基本上超过直接malloc方式,在200w次申请和释放的情况下,单线程release版情况下,mempool比直接malloc快110倍。而在4个线程情况下,mempool要比直接malloc快7倍左右。以上测试只是申请速度的测试,在不同的压力情况下,测试结果可能会不同,测试结果也不能说明mempool方式比malloc方式稳定。

    小结:内存池基本上满足初期设计目标,但是她并不是完美的,有缺陷,比如,不能申请大于256字节的内存空间,无内存越界检查,无内存自动回缩功能等。只是这些对我们的影响还不是那么重要。

由于这是一个公司项目,代码涉及版权,所以不能发布出来。如果你想做自己的内存池,可以与我联系ugg_xchj#hotmail.com.

 

[1] [2]

责任编辑 webmaster

 
 
 
 
 
评论更多>>
 
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •