您的位置:首页 > 移动开发 > Objective-C

boost pool, object_pool实现

2011-09-01 16:39 519 查看
一:

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
;

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++爱好者读下源码。任何技术类的文档,都不会比直接阅读

源码来的实在。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: