boost pool, object_pool实现

来源:互联网 发布:第三次经济普查数据库 编辑:程序博客网 时间:2024/06/16 22:09

一:
class simple_segregated_storage; boost/pool/simple_segregated_storage.hpp
simple_segregated_storage (以下用SSS替代)实现一个单向链表,
链表的每个节点是一个固定大小并且未被使用的内存块。

具体做法:
1,void* first 标记链表头。//如果first=NULL,则这是一个空链表
void* next = *first; //第二个节点,如果next=NULL,则表明first没有子节点。
这里的实现有个小技巧:举个小例子
char buf[12]; 分成3块,每块大小为4个字节。
每个指针的大小是4个字节,buf[0]-b[3]刚好能保存下一个节点的地址。
同理 buf[4]-b[7], b[8]-[11]都能保存下一个节点的信息。
(void*)(buf+8) = 0,表示连表尾部。
这样这个链表不需要多余的空间,只需要一个first便可以了。

类中nextof函数返回 ptr的下一个节点。
void *& nextof(void * const ptr)
{ return *(static_cast<void **>(ptr)); }

SSS类实现的功能:
1,void add_block(void* const block, const zize_t size,
        const size_t small_size);
     把一段连续的大小为size大内存块加入到链表中,每个块大小为small_size

2,void add_ordered_block(void* const block, const zize_t size,
        const size_t small_size);
     功能同上,如果原链表有序,则保证插入后链表有序。

3,void* malloc();
     申请一个块。
4,void free(void * const chunk);
     释放一个块,加入链表中。
5,void ordered_free(void * const chunk);
     同free,如果原链表有序,则此插入后仍然有序。(前后都是升序)
6,void * malloc_n(size_type n, size_type partition_size);
     申请n块, 大小为partition_size的连续内存。有序链表更容易申请成功。
     这个函数只是遍历链表
           void * iter = nextof(start);
           while (--n != 0)
           {
                   void * next = nextof(iter);
                   if (next != static_cast<char *>(iter) + partition_size)
                   {
                         start = iter;
                         return 0;
                   }
                   iter = next;
           }
           return iter;
7,void free_n(void * const chunks, const size_type n,
           const size_type partition_size);
    调用add_block函数,把n块每块大小为partition_size的连续内存插入链表。

8,void ordered_free_n(void * const chunks, const size_type n,
          const size_type partition_size);

二:class pool // boost/pool/pool.hpp
1: class PODptr; 这个类保存一个固定大小的内存块,SSS类保存的是PODptr中内存块分割后的小块。
      成员:
          char * ptr;    // 块的首地址
          size_type sz; // 块的大小
      如果PODptr保存100个块,每个块的大小是10。则:
      char *buf = new char[100*10 + 8]; 
      ptr = buf; size=100*10+8;
        其中前100个块交给SSS来管理((ordered_)add_block)。
        最后8个字节,可以看成1个指针(buf[1000]-buf[1003])和1个unsigned int(buf[1004]-buf[1007])。
        这个指针保存下一个PODptr(如果存在的话)的内存快的位置,
        这个unsigned int值,保存下一个PODptr内存快的大小。
      其实PODptr是一个链表的节点...。

         pool用两个类SSS和PODptr来管理内存。用户从boost::pool中申请内存的时候,
     首先判断SSS链表是否为空,如果非空,则直接分配SSS链表保存的内存快,并把分出去的块从链表中删除;
     如果SSS为空,则申请一个大的内存块,PODptr来保存这个大内存快的信息。并把这个大的内存块分割成N个
     小的内存块,分配给SSS。

2,class pool;
     pool 提供的功能:
     1,void * malloc();
     2,void * ordered_malloc(); // 这个是在SSS空间不够的时候,需要增加新的PODptr,保证PODptr有序。
     3,void * ordered_malloc(size_type n);
     
     4,void free(void * const chunk);
        void orderd_free(void* const chunk);
     
     5,void free(void * const chunks, const size_type n);
     6,void ordered_free(void * const chunks, const size_type n); 有序释放(添加到SSS中,并不是deleted掉)
     
     7,bool is_from(void * const chunk); //判断chunk是否是此pool分配出去的。
     8,release_memory();//释放掉pool的所有内存(delete)。这个函数会在pool的析构函数中调用。
     

     1: pool只能分配固定大小的内存块。(SMALL_BLOCK_SIZE)
     2: pool的默认的策略,第一次先申请32块,即第一个PODptr中内存大小是 32* SMALL_BLOCK_SIZE + 8;
         之后每次申请前一次申请空间的2倍。(32,64,128...),这样做空间利用率为1/(2^N/N +1) > 50%,(N表示第几次申请)
         并且不会频繁向系统申请大的内存块。如果你最多需要N块内存,则向系统申请内存的次数是lgN,lgN是一个很小的复杂度。
     3: pool如果不析构,空间只会慢慢变大,即巅峰期,你用掉了100000个块的内存,即使全部还给了pool,pool也不会释放PODptr。
         这个与stl::vector实现相同。在vector对象不析构的时候,空间只会变大(assign,resize操作除外,不过pool没有提供类似操作)。
     4: pool的实现非常好,很多人可以用更精简的代码写出更高常数效率的pool,但是boost::pool的实现方方面面考虑很周到。


三:
         boost作为一个极其很庞大并且牛X的C++后备库,当然不会紧紧提供这些基础功能。
     个人感觉boost一直致力于让C++看起来更像高级语言(甚至脚本语言), 效率又远远高于脚本语言和其他高级OO。
     给boost的使用者提供更好的体验。
     boost::object_pool 是一个真正不用释放内存,在几乎所有情况下都不会有内存泄漏的内存管理器。
     
     声明: 
         tempate<class element_type>
         public object_pool : publib pool{}
     成员函数:
     1,element_type * construct(); 申请一个用默认值的对象指针。
     
     //析构对象,并把内存交给pool,C++使用者的一个良好习惯或者说C++的一个限制是,
     //很多东西一定要成对使用(譬如申请内存new,delete;文件IO open,close;互斥锁lock,unlock)
     //但是使用ojbect_pool 申请对象,却不需要destory
     2,void destroy(element_type * const chunk);//destory 会调用chunk的析构函数
     
     class Sample
     {
         char *buf;
         size_t buf_size;
         public :
             Sample(){ buf = new char [1000]; buf_size=1000;}
             ~Sample() { delete[]buf; }
     };
     
     Sample* pa = new Sample();
     delete pa; // 首先调用 pa.~Sample(); 释放buf申请的内存,其次释放pa所占用的内存。
     
     pa = obj_pool.construct(); // 从pool中分配一个空间给pa,并调用pa的构造函数。
     obj_pool.destroy(pa); // 先调用pa.~Sample(); 然后把pa所占的空间交给pool。
     
         对于大多数内存管理器,必须成对的调用construct和destroy。因为pool对象析构的时候,只会释放对象占用的空间,
     但是不会调用对象的析构函数,此时,对象动态申请的内存就会泄露掉。(此例子中的buf指向的内存)。
     
         但是object_pool则允许你忘记调用 destory。
     boost::object_pool做法:
     object_pool 会一直保证 SSS链表和PODptr链表有序,在object_pool的析构函数中,会帮你调用对象的析构函数。
     做法就是遍历PODptr链表,如果其中某个PODptr的某一块内存不在SSS中(即不是空闲内存,没有被释放),
     则调用此块内存所指向对象的析构函数。
     
     object_pool的析构函数实现大概实现如下:
     PODptr* head;
     for(PODptr* node = head; node != NULL;)
     {
         for( i=0; i<node.size(); ++i )
         {
             if ( node[i] != SSS.first )
             {
                 // node的第[i]个节点没有被释放。
                (element_type *)(node[i]).~element_type(); //调用析构函数。
                 sss.first = sss.first.next();
              }
         }
         PODptr* tmp = node;
         node = node.next();
         delete tmp;
     };
     
     object_pool析构的复杂度是O(N)
          
     由于要一直保证链表SSS有序。(当然也需要保证PODptr有序,但是PODptr的链表大小相对很小,lg级别的)
     
     在一个有序链表中插入一个节点的最坏复杂度是0(N)。
     为了保证SSS有序,每次destory的最坏复杂度是0 (N) 。
     
     很容易构造一种最坏复杂度的情况:
     const int N = 10000;
     Sample* a[N];
     for( i=0; i<N; ++i)
          a[i] = pool.cunstruct();
     for(i=0; i<N; ++i)
          pool.destory( a[i] );
     
     每次destory的复杂读是O(N)。总复杂度是0(N*N)。
     
     如果pool的生存周期比较长,那总的destory的复杂度会高的离谱,在一些特别需要效率的程序中,完全不可接受。
     
     一种可能的优化方案: 每次destory并不保证 SSS有序,而是在 pool_object的析构函数中,对链表SSS排序,
     这样每次申请,释放的平均复杂度是0(1)。析构的复杂度是0(NlgN)。在pool_object对象生命周期相对比较长的情况下。
     这个复杂度完全可以接受。     
     当然,优化只是针对个别使用场合。不可能存在一个方法能够使得在任意情况下都是最优的。


       我目前的大多数程序,都是在程序开始时需要申请一个pool对象,在程序结束的时候释放这个pool对象,
    所以我更倾向于在析构中排序的策略。当然,我的这种使用内存分配的方法,完全没有必要使用boost::object_pool。
    假如中间某次不调用destory,必然会导致程序内存占用越来越大,所以,object_pool的对象生命周期应该不要太长。
   
       如果boost::object_pool 保证每次申请释放的复杂度是0(1),pool对象析构的复杂度即使是0(N*N)我也可以接受。
   一个在linux服务器上跑的程序,总是希望它能一直跑下去。
   
   PS:boost::object_poll 的实现用了很多C++技巧,强烈建议C++爱好者读下源码。任何技术类的文档,都不会比直接阅读
   源码来的实在。