内存池比malloc的好处

以下内容摘自侯捷老师的《Boost技术与应用》       

 欲分配一块内存,C语言用的是malloc()函数家族,例

void* p = malloc(1024);

欲创建一个对象,C++语言用的是new表达式,例如:

Foo *p = new Foo;

new表达式实际上分为三个步骤,其一分配内存,其二

转型,其三调用构造函数:

//以下是C++ 编译器对new表达式的分解动作
Foo *p;
try {
void* mem = Foo::operator new( sizeof(Foo)); //分配内存
p = static_cast<Foo*>(mem); // 转型(cast)
p->Foo::Foo; //构造
} catch( std::bad_alloc )
{
// 若分配失败,不执行构造函数
}


        如果编译器没能找到上述的Foo::operator new(),就会改而调用全局函数::operator new(),那是C++程序库提供的内存分配基本工具,功能相当于malloc(),这一点可从Borland C++源码获得清晰的印证:

inline void* _RTLENTRY
operator new (size_t size, const std::nothrow_t &)
{
size = size ? size : 1;
return malloc(size); // ::operator new 调用malloc()
}

        

无论C或C++,追根究底谈其内存分配时一定得接触malloc()。此函数的行为可从Doug Lea所写的知名版本窥知,此版本被各大编译器采用,源码见Doug Lea 的个人主页http://gee.cs.oswego.edu/dl/。追踪这份源码不是件容易的差事,我手上这份源码的.h长达679行,.c长达5621行,而且市面上找不到完整论述介绍其中的数据结构或算法。但纵使未能完完全全了解malloc()的行为,这里一定要让你知道,malloc()分配出来的内存区块除了满足客户索求的大小,另附带了一个不为人知的区域,一般昵称为cookie(小甜饼),见图1。cookie 的大小和其储存的信息及格式,视不同的实现而有所不同。我曾做过一个试验,在刚开机的情况下(此时可用内存的分布还很干净,未被乱切乱拼)分配若干区块,获得若干指针,然后将相邻各指针相减,这便假设是cookie的大小(实验结果不是4就是8,取决于编译器实现)。再将每一指针的前4或前8个bytes印出来,便是cookie内容。但是不知其格式该如何打印呢?瞎猫碰死耗子,以整数格式打印看看。结果发现BCB的cookie 总是(并且只是)记录着区块大小。其它编译器就没有这么“清纯”。

        区块之前方带着一块“小甜饼”的必要性很容易理解:当我们将内存归还系统,我们调用的是free() (或?::operator

delete())并且只给予一个指针指向先前以malloc()获得的内存。这种情况下如果没有“小甜饼”帮助,free()根本不知道客户意欲释放的区块有多大。cookie甚至会记录区块大小以外的信息,例如该区块和其系统中的邻块之间的关系等,以利系统回收后做最佳安排。这些信息的设计取决于编译器实现。

        为应付大小不一的各种需求,cookie 乃是必要之“恶”。但也就是这个cookie为C++程序中常见的一种情况带来很大

浪费:如果客户需要大量小区块(比方说100,000个对象,每个对象16 bytes),那么每个区块都带cookie 就是一种浪费,

因为大量小区块中的每一区块尺寸都相同,应该可以有更好的设计,不需要cookie 那份“区块尺寸”的记录。围绕这个概念的相关设计几乎全都基于一种相同的思考,就是Pooled Allocation(池式分配)。为了拿掉cookie,通常的做法是由管理器(程序库)挖一大块内存自行细切(不带cookie,每一块尺寸都刚好符合客户需求)。这些被细切的小区块往往以linked list管理,一整串小区块也就被称为free list。当客户需要这种尺寸的小区块时,就由程序库从free list 中给出一个,并维护linked list的正确链接。这种想法好似把一大块内存当成一个池子(pool)来供应“个头一致”的小东西,所以被称为池式分配。“pool”的另一个意义是“共享物”,当动词解则是“联营”,Pooled Allocation的意思正是“联营系统下的内存分配”。

        C++程序中,客户索求大量固定尺寸的小区块,这种情况很常发生(相当于制造出大量对象,每个对象都小小的)。

也非常可能客户要的尺寸有三两种,因此一个好的 PooledAllocation设计,尤其是作为必须应付各种可能情况的角色而

存在的程序库,最好能同时供应(维护)多个free lists,每个list负责供应某一特定大小的区块。Pooled Allocation不但

大量节省空间,亦巨幅节省时间,因为它大量减少了向系统索求内存的次数。


如何设计Pooled Allocation

        敏锐的你很快会想到,为了节省一个cookie(4 或8 个bytes)却赔上维护linked list所需的指针(单向list需要一个指针,双向list需要两个指针),岂不是偷鸡不成蚀把米?但事实上我们不需要为每个区块各自准备指针,因为区块在尚未给出之前,也就是当区块还在吾人自定义的分配系统(程序库)的掌握下时,其空间可被程序库使用,对客户无任何影响,而这些空间(现实至少4 bytes)就足以做出至少一个指针了。这种从区块本身挖空间建立指针的做法,在《Small Memory System, Patterns for system with limited memory》(中译本“内存受限系统之设计模式”)中称为“嵌入式针”(Embedded Pointers),见图2。如图:给出的区块不再被free list管理,未给出的区块前4 bytes被当做维护linked list所必须的指针。 这种设计可被相当精简地实现。《C++Primer, 3/e》15.8节P.765和《Effective C++, 2/e》条款10都列有对池式分配的片段举例,简单地说就是为各自例中的class重载operator new和operator delete(用以接管客户提出的申请),并在operatornew中管理单一free list。两例的唯一差别是前一例未使用嵌入式指针,后一例使用了嵌入式指针,刚好让我们做出比较。如果把上述功能独立出来放在一个分离而专用的class内(假设名为Pool),由它提供接口函数(通常名为allocate() 和 deallocate()),让客户的每个classes重载operator new和operatordelete并且其内不再自行管理free list也不直接调用malloc()和free(),而是改调用Pool::allocate()和Pool::deallocate(),这就把池式分配干净地切割了出来,又让客户的classes不知不觉用上它。这便是所有C++程序库池式分配系统的雏型。下面我要描述的每一个C++程序库的内存分配系统,雏型都如以上描述。至于其各显神通的差异性,可从各张示意图中轻易感受。进行之前让我们统一术语的用法,在池式分配系统中,每次向系统要的大区块通常称为chunks,chunk内切割出来尺寸相同的小区块称为blocks。STL, MFC, Loki皆沿用这种称呼习惯,但Boost刚好相反。讨论到Boost时我会再次提醒你。此外,将内存还给程序库分配系统的动作,我会说归还(deallocation),将内存还给系统的动作,我会说释放或释还(free)。

转载于:https://my.oschina.net/lcxidian/blog/382254

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值