C++内存池(附源码).docx

上传人:wj 文档编号:82144 上传时间:2022-10-02 格式:DOCX 页数:16 大小:18.32KB
下载 相关 举报
C++内存池(附源码).docx_第1页
第1页 / 共16页
C++内存池(附源码).docx_第2页
第2页 / 共16页
C++内存池(附源码).docx_第3页
第3页 / 共16页
C++内存池(附源码).docx_第4页
第4页 / 共16页
C++内存池(附源码).docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

C++内存池(附源码).docx

《C++内存池(附源码).docx》由会员分享,可在线阅读,更多相关《C++内存池(附源码).docx(16页珍藏版)》请在冰豆网上搜索。

C++内存池(附源码).docx

C++内存池(附源码)

C++内存池(附源码)

  前段时间阅读了Nginx的源码,其对内存⾼效的管理给我留下了深刻的印象,⽽内存管理的核⼼便是内存池。

于是想⾃⼰实现⼀个

C++版本的内存池,这⽅⾯当然还是STL的内存池最为经典,所以免不了参悟借鉴。

内存池的概念早已经是⽼⽣常谈,然⽽把内存池实现的⾼效安全仍是个⽐较艰巨的问题。

内存池的原理简单来讲就是⼀次性的向系统申请⼤量的内存,之后再有内存请求的时候,如果内存池的内存⼤⼩能够满⾜请求,就从内存池⾥分配,不必再进⾏系统调⽤,从⽽实现性能提升,⽽多次的内存申请系统调⽤,很容易⽣成内存碎⽚⽽造成内存浪费。

池的概念⼤体如此,线程池,进程池⽆出其右。

内存池的实现主要解决的问题有:

   1.内存池的块管理

   2.内存的分配和回收

   3.⼤块内存的分配和回收

   4.对象初始化

内存池的块管理

  这⽅⾯可以直接参考STL的分配器的实现,SGISTL在进⾏内存分配时,默认使⽤了⼀个内存池。

这个内存池的内存块从8Byte开始,每递增8Byte都⽣成⼀系列链表管理的内存块,⼀直到128Byte结束。

内存块定义为:

unionMemNode{

MemNode*_next;

char_data[1];

};

  union每个成员的起始地址都是开头的位置,所以每次仅能使⽤⼀个成员,在链表中由_next指向下个内存块的地址,在分配内存时由_data指向内存⾸地址,长度为1的数组放在结构体最后⼀个成员位置,可以访问给结构体多分配的地址空间,这种技术叫做柔性数组。

这样做的好处减少了对内存块管理时额外的内存损耗。

想想我们学习数据结构时实现的链表,都是通过结构体的⼀个成员来指向下个节点的地址,多出了⼀个指针4Byte的内存消耗。

参考STL,我们内存块的管理如下图所⽰:

  有同学要问了,那我要是申请⽐128更⼤的内存怎么办?

SGI这⾥就直接⾛正常的内存申请,还是会有系统调⽤产⽣。

因为系统对于程序请求的内存,管理时也会⽣成额外的内存控制数据占⽤内存,这样申请的内存越⼩,额外占⽤的内存⽐例就越⾼。

我们每次申请指定量的内存,然后将内存格式化到块管理的数组链表中。

char*res;

size_tneed_bytes=size*nums;

size_tleft_bytes=_pool_end-_pool_start;

//内存池够⽤

if(left_bytes>=need_bytes){

res=_pool_start;

_pool_start+=need_bytes;

returnres;

}elseif(left_bytes>=size){

nums=left_bytes/size;

need_bytes=size*nums;

res=_pool_start;

_pool_start+=need_bytes;

returnres;

}

size_tbytes_to_get=size*nums;

if(!

is_large){

if(left_bytes>0){

MemNode*my_free=_free_list[FreeListIndex(left_bytes)];

((MemNode*)_pool_start)->_next=my_free;

_free_list[FreeListIndex(size)]=(MemNode*)_pool_start;

}

}else{

free(_pool_start);

}

_pool_start=(char*)malloc(bytes_to_get);

//内存分配失败

if(0==_pool_start){

throwstd:

:

exception("Therememaryisnotenough!

");

}

_malloc_vec.push_back(_pool_start);

_pool_end=_pool_start+bytes_to_get;

returnChunkAlloc(size,nums,is_large);

  将返回的内存添加到块管理队列中

my_free=&(_free_list[FreeListIndex(size)]);

*my_free=next=(MemNode*)(chunk+size);

for(inti=1;;i++){

current=next;

next=(MemNode*)((char*)next+size);

if(nums-1==i){

current->_next=nullptr;

break;

}else{

current->_next=next;

}

}

内存的分配和回收

  每次从系统申请内存时都通过⼀个辅助函数将内存增到为8的倍数,上层请求内存时寻找最⼩能容纳当前请求的头节点索引

//获取size最⼩8的倍数

size_tRoundUp(size_tsize){

return((size+__align-1)&~(__align-1));

}

//获取容纳当前size的最⼩内存块索引

size_tFreeListIndex(size_tsize){

return(size+__align-1)/__align-1;

}

  当找到索引位置时,如果内存块不为空,则取出当前内存块,将之后的链表节点向前移动,如果内存不够的话,再次向系统请求新的内存。

std:

:

unique_lock lock(_mutex);

MemNode**my_free=&(_free_list[FreeListIndex(sz)]);

MemNode*result=*my_free;

if(result==nullptr){

void*bytes=ReFill(RoundUp(sz));

memset(bytes,0,sz);

returnbytes;

}

*my_free=result->_next;

memset(result,0,sz);

returnresult;

  内存回收时与此理相同,通过辅助函数找到索引位置,将内存块放⼊⾸部位置,之前的内存块后移。

MemNode*node=(MemNode*)m;

MemNode**my_free=&(_free_list[FreeListIndex(len)]);

std:

:

unique_lock lock(_mutex);

node->_next=*my_free;

*my_free=node;

m=nullptr;

⼤块内存的分配和回收

  通过以上的内存管理,我们⾜以解决⼩块内存的⾮配和回收,但是还可能存在另⼀种需求,类似Nginx内存池有⼤块内存的管理,我们在实际开发中也会⽤到诸如接收发送缓存之类的需求。

这⾥添加增加⼀个新的类,以管理⼤块的内存块,⽽且要⽀持动态的增减:

  通过⼀个vector来管理池中空闲的内存块,需要注意的是析构时要将所有从池中申请的内存还给内存池,不然就需要⾃⼰⼿动释放。

申请内存块的⽣命周期管理可以交给。

对象初始化

  与C语⾔实现内存池的不同之处在于,C语⾔可以只负责内存的分配⽽不⽤管内部数据的初始化,因为C语⾔没有对象的概念。

但是在C++中,我们不仅仅要负责内存的分配,还要调⽤构造函数负责对象的初始化。

⼤家知道C++中的new操作符,⼀是负责内存申请,⼆是调⽤构造函数实现对象初始化。

⽽C++中可以通过可变模板参数来实现任意数量任意参数的函数转发,再辅之std:

:

forward完美转发,即可实现构造函数的调⽤功能。

所以我实现的内存池对外提供内存申请的接⼝有三个:

//forobject.invocationofconstructorsanddestructors

template

T*PoolNew(Args&&...args);

template

voidPoolDelete(T*&c);

//forcontinuousmemory

template

T*PoolMalloc(intsize);

template

voidPoolFree(T*&m,intlen);

//forbulkmemory.

//returnonebulkmemorynode

template

T*PoolLargeMalloc();

template

voidPoolLargeFree(T*&m);

  这样每次请求和释放都需要调⽤接⼝,C++对这种操作最熟悉不过,我们交给智能指针来管理即可。

还有这⾥为什么没有重载new操作符来呢?

因为new和delete的重载函数只能是static函数(因为new对象的时候,对象还没有创建),所以内存池的api通过重载new和delete实现,看起来很美好,但实际上是⾏不通的。

我们要创建内存池的对象,每个内存池的对象管理的都是不同的内存。

下⾯看下PoolNew调⽤构造函数的过程。

template

T*CMemaryPool:

:

PoolNew(Args&&...args){

intsz=sizeof(T);

if(sz>__max_bytes){

void*bytes=malloc(sz);

T*res=new(bytes)T(std:

:

forward(args)...);

returnres;

}

std:

:

unique_lock lock(_mutex);

MemNode**my_free=&(_free_list[FreeListIndex(sz)]);

MemNode*result=*my_free;

if(result==nullptr){

void*bytes=ReFill(RoundUp(sz));

T*res=new(bytes)T(std:

:

forward(args)...);

returnres;

}

*my_free=result->_next;

T*res=new(result)T(std:

:

forward(args)...);

returnres;

}

  到这⾥基本上所有的功能都已经实现完毕。

但是既然我们⽀持创建内存池的对象,那什么时候释放内存池占有的内存呢?

当然是析构函数中!

但是怎么释放呢?

我们是通过malloc库函数申请的内存,释放的时候⾃然是去调⽤free释放。

但是我们不能通过循环块的数组和链表去释放内存。

因为我们申请的时候是⼀整块去申请的,释放的时候只要通过每次申请的头地址去释放即可。

所以我在这⾥添加了⼀个辅助的std:

:

vector来存储每次申请内存的地址,释放的时候只遍历这个std:

:

vector即可。

//声明

std:

:

vector _malloc_vec;

//存储

_pool_start=(char*)malloc(bytes_to_get);

if(0==_pool_start){

throwstd:

:

exception("Therememaryisnotenough!

");

}

_malloc_vec.push_back(_pool_start);

//释放

for(autoiter=_malloc_vec.begin();iter!

=

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1