勤能补拙,Expter

成都游戏Coder,记录游戏开发过程的笔记和心得!

基于策略的一种高效内存池的实现

一.XXX
      1)概念说明
          这里不再具体描述内存池的概念和作用,需要了解请看http://baike.baidu.com/view/2659852.htm?fr=ala0_1_1

      2)描述
         在开发一个长时间运行的服务器程序时,一般频繁的向操作系统动态申请内存,而采用堆new分配,速度较慢,而且如果一个程序频繁的申请小内存块,很容易产生内存碎片,每次查找相对较慢。
        因为堆是向高地址扩展的数据结构,每次内存分配其实都是进行虚拟内存分配,都会建立虚拟内存的到物理内存的映射,而是分配的一种不连续的内存区域,由于系统是用链表来存储的空闲内存地址的,而链表的遍历方向是由低地址向高地址。     
       这里还有一篇关于关于频繁的内存分配性能分析 ,图文并茂的讲得很详细。
     
      3)实现目标. 
       而设计内存池的目的是为了保证一个程序长时间高效的运行,而该程序对内存申请频繁,为了减少系统内存碎片的产生,合理分配管理用户内存,从而减少系统中出现有效空间足够,而无法分配大块连续内存的情况。

         关于实现一个高效与稳定内存池模块有如下目标:
         A.如何实现内存的快速分配
         B.如何实现内存的快速释放
         C.如何管理内存池的稳定与效率.  
         注:本文介绍的内存池管理效率相对较高,且可以针对任意大小内存分配.... 

  二.设计
      设计前我们假设内存申请很频繁,而且申请小于在5120byte的远远大于5120byte字节。
  
      为了让设计的内存池的使用更具有通用型和高效性,这里大致介绍通过需求不同指定一种基于内存需求策略的方式设计出一种内存池。   
      及小于5120字节的采用小块内存分配,大于5120的通过操作系统分配,内存池管理.

      此内存池包括2中分配方式,介绍分配如下:
      1)  个数固定的不定长的静态内存分配(BlockPool)。           
       A.设计思路
      这种主要是根据程序中不同对象的大小而指定的一种策略,对于每一种大小又是通过一个链表链表管理,每个链表的节点的内存大小不同,而为了方便内存的管理,一般在程序初始化的时候针对不同的策略大小分配一块内存块,然后把此内存块划分为多个节点保存到链表中,每一个链表中保存的将是空闲的节点。
      
       B.基本数据结构  

 基本数据结构:
       
/// 指定的策略
       enum eBuff_Type
{
    eBT_32 
=0,
    eBT_64,
    eBT_128,
    eBT_256,
    eBT_512,
    eBT_1024,
    eBT_2048,
    eBT_5120,

    eBT_END,
}
;

  /// 定一个双向链表的节点2个指针   

/// 
///  file  BaseNode.h
///  brief 一个双向链表需要的前后指针节点
/// 

 
#pragma  once 
template 
< typename  T >
class  CNode
{
    typedef  T  Node;
public:
    Node
*    next;
    Node
*   prev;

    CNode()
    
{
        next 
= prev = NULL;
    }

}
;
/// 节点
struct  Buffer: CNode<Buffer>
{
    eBuff_Type   type;
}
;

/// 不同大小的数据节点.

struct Buffer32 :  Buffer {    char buf[32];   };
struct Buffer64 :  Buffer {    char buf[64];   };
struct Buffer128 : Buffer {    char buf[128];  };
struct Buffer256 : Buffer {    char buf[256];  };
struct Buffer512 : Buffer {    char buf[512];  };
struct Buffer1024: Buffer {    char buf[1024]; };
struct Buffer2048: Buffer {    char buf[2048]; };
struct Buffer5120: Buffer {    char buf[5120]; };

 

///TlinkedList为一个链表
///这里包括eBT_END数组

TlinkedList<Buffer>  m_MemPool[eBT_END];


   m_MemPool主要结构图如下:
     
               
       C.性能分析
          分配
            这里策略只是针对小于eBT_5120Byte的内存进行分配,根据传入的大小直接Hash利用m_MemPool[idx]返回链表头,返回Buffer节点的buf数据块。
          释放:根据传入要释放的Mem内存地址     

 /// 获得偏移地址
Buffer* buf = (Buffer_Face*)( (char*)Mem - offsetof(Buffer_Face,buf) );

             通过偏移地址即可获得buf的地址,buf里面包括type获得m_MemPool的索引,然后把释放的节点重新Push到m_MemPool[type]中。
        
          性能: 插入,分配都是0(1)


     2)  完全动态分配内存(HeapPool)。
          介绍了上面的静态内存分配,其实是在利用已经分配好了的内存块里面在进行查找,释放也是直接根据传入的直接直接保存到缓存表中。
           A.介绍下需要的基本数据结构

struct  listNode : CNode<listNode>
    
{
        
long  size;
        
/// 指向的内存
        char* ptr;
    }
;
    
struct  HeapNode
    
{    
        
/// memory request rate
        __int64              m_Count;
        
/// all free memory list
        TlinkedList<listNode> m_FreeList;
        
/// using memory list
        TlinkedList<listNode> m_Used;
        HeapNode( )
        
{
            m_Count 
= 0 ;
            m_FreeList.ReleaseList();
            m_Used.ReleaseList();
        }

    }
;

         typedef std::map<unsigned long,HeapNode* > MHEAPLIST;  /// 其中map的key是分配的大小。
         HeapNode的结构图如下:
              
            B.设计思路
                 根据策略程序一般大于5120Byte字节的相对比较少,而程序请求大小也是相对比较规则,散列不是太大。
                 HeapNode有2个链表m_FreeList和m_Used,其中链表的节点如图ListNode所示
                 listNode有一个ptr表示需要配的内存,ptr指向的前8个地址为listnode的地址值(根据Cpu的最大寻址为64位),ptr+8则是分配的内存地址,为什么这么设计呢?
                我是这样想的外界使用内存空间为data区域,那么我们释放的时候的只是需要传入data的地址,即可通过求出listnode的地址,    

/// 根据MemAddr的地址求listnode地址
__int64   pAddr = *(__int64*)((char*)MemAddr-8);
listNode
* pNode =  (listNode*)pAddr;

               得到listNode地址后即可进行找到对应的HeapNode,然后进行释放或者放入缓存列表的操作。              
               关于HeapNode的管理,为了节约内存我们不可能一直申请内存而不释放,所以我们约定m_FreeList只是保存m_Used中一半大小的结点。当m_FreeList过多的节点时需要释放一定空间。(这个约定可以根据不同的需求而制定).

               上面介绍了为什么如此的设计这个数据结构,下面介绍分配策略。分配的时候先查找是否有缓存数据,没有则分配一个,否则直接返回m_FreeList的一个listNode(结点)的ptr+8;
               

            C.性能分析
                 通过上面描述可以确定基于完全动态分配的效率
                 分配的时候  lg(n);
                 释放的时候 lg(n);
                而map的查找基于AVL树,所以查找基本是常量型的。               
 
  三.实现
      上面只是介绍了分配方式,下面介绍实现。
       通过上面描述可知,对于大于5120byte的内存分配采用HeapPool分配,否则采用BlockPool分配。
       为了方便外界使用我们使用一个CMemFactory内存分配工厂,通过使用者申请Size和释放pAddr即可快速进行分配和释放。

  代码如下:
       http://code.google.com/p/tpgame/source/browse/#svn/trunk/MemPool/MemPool

 具体代码打包如下:     
     /Files/expter/Pool.rar
       

注:如需了解跟多的内存池是实现可以阅读STL SGI,  Loki,  Boost内存池的实现...


附带最新内存池,实现和介绍...
http://www.cppblog.com/expter/archive/2011/01/18/138787.html

posted on 2010-04-14 23:23 expter 阅读(5356) 评论(11)  编辑 收藏 引用 所属分类: 其他学习笔记工作笔记算法与数据结构Ai

评论

# re: 基于策略的一种高效内存池的实现 2010-04-15 08:45 ccsdu2009

不错  回复  更多评论   

# re: 基于策略的一种高效内存池的实现[未登录] 2010-04-15 09:04 Megax

你这个内存池本身的数据结构就不够优化。一些数据本身就是可以放在内存池中的Data块的。另外也不知道你为什么选择5120,我记得windows的也内存是4KB.还有你的查找居然是用Map....而且是对指针进行Hash.
Loki当中有一个让人叹为观止的内存池实现,建议你看看,极其小巧与精致。  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-15 09:08 expter

@Megax
map的key是long,不知道哪儿用了指针类型。
我的第一种实现方式就是借鉴loki库...  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-15 10:57 欣萌

有个疑问 为什么是5120

是不是太大了啊。  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-15 11:24 expter

@欣萌
这个根据不同的程序需求而不同,可以指定不同的策略。  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-17 01:58 qiaojie

不会使用free list数据结构的memory pool都是垃圾  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-17 03:09 欲三更

mempool大概就是这样子吧, 我应该写不出这么好:)

但是: 1. 如果我要申请的是8字节这么大的内存, 那么内存浪费的比率就非常之大!
2. struct Buffer: CNode<Buffer>
{
eBuff_Type type;
};

就说这个结构, 就为了记录一个大小, 每个对象里都要存储一个变量, 不觉得浪费么?  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-17 03:10 欲三更

@qiaojie
要是人家不用list呢?  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-17 09:48 expter

@欲三更
记录大小是为了用空间换一次查找的过程。。
如果我们只需要传入内存地址,那么可以通过偏移地址即可得到eBuff_Type type,直接可以得到存储索引进行内存释放。  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2010-04-17 18:58 欲三更

@expter
我知道你是这个意思, 但还是觉得太奢侈了.
反正你只把相同尺寸的Buffer连在一起, 能不能这样简单点搞搞?:

template <size_t s>
struct Buffer
{
----static size_t Size;
----union
----{
--------struct
--------{
------------Buffer<s> *pNext;
------------Buffer<s> *pPrevious;
--------}p;
--------char m_pBuf[s];
----}
};

然后模板显式实例化顺便把静态成员也赋上值.  回复  更多评论   

# re: 基于策略的一种高效内存池的实现 2012-01-20 13:15 灰兔

@qiaojie
apr_pool_t呢?~ ~~  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理