服务器性能的关键在于内存。从收包到解析,到消息内存的申请,到session结构内存的申请都要小心处理,尽量减少内存数据copy,减少内存动态申请,减少内存检索。为达到这个目的,不同的地方有不同的方法,比如常见的包解析,使用缓冲区偏移以及长度来标识包内字段信息;内存使用量固定的系统,系统启动就申请好所有需要的内存,初始化好,等待使用的时候直接使用;基于license控制的系统,根据license的数量,一次性申请固定数量内存等......。本文不再总结这些特性方案,重点说下常见的通用的内存池缓存技术。

内存池可有效降低动态申请内存的次数,减少与内核态的交互,提升系统性能,减少内存碎片,增加内存空间使用率,避免内存泄漏的可能性,这么多的优点,没有理由不在系统中使用该技术。为了给内存池技术寻找基石,先从低层的内存管理看起。

一、linux内存管理策略
     linux低层采用三层结构,实际使用中可以方便映射到两层或者三层结构,以适用不同的硬件结构。最下层的申请内存函数get_free_page。之上有三种类型的内存分配函数
    (1)kmalloc类型。内核进程使用,基于slab技术,用于管理小于内存页的内存申请。思想出发点和应用层面的内存缓冲池同出一辙。但它针对内核结构,特别处理,应用场景固定,不考虑释放。不再深入探讨。
    (2)vmalloc类型。内核进程使用。用于申请不连续内存。
    (3)brk/mmap类型。用户进程使用。malloc/free实现的基础。
    有关详细内容,推荐http://www.kerneltravel.net/journal/v/mem.htm。
    二、malloc系统的内存管理策略
    malloc系统有自己的内存池管理策略,malloc的时候,检测池中是否有足够内存,有则直接分配,无则从内存中调用brk/mmap函数分配,一般小于等于128k(可设置)的内存,使用brk函数,此时堆向上(有的硬件或系统向下)增长,大于128k的内存使用mmap函数申请,此时堆的位置任意,无固定增长方向。free的时候,检测标记是否是mmap申请,是则调用unmmap归还给操作系统,非则检测堆顶是否有大于128k的空间,有则通过brk归还给操作系统,无则标记未使用,仍在glibc的管理下。glibc为申请的内存存储多余的结构用于管理,因此即使是malloc(0),也会申请出内存(一般16字节,依赖于malloc的实现方式),在应用程序层面,malloc(0)申请出的内存大小是0,因为malloc返回的时候在实际的内存地址上加了16个字节偏移,而c99标准则规定malloc(0)的返回行为未定义。除了内存块头域,malloc系统还有红黑树结构保存内存块信息,不同的实现又有不同的分配策略。频繁直接调用malloc,会增加内存碎片,增加和内核态交互的可能性,降低系统性能。linux下的glibc多为Doug Lea实现,有兴趣的可以去baidu、google。
    三、应用层面的内存池管理
    跳过malloc,直接基于brk/mmap实现内存池,原理上是可行的,但实际中这种实现要追逐内核函数的升级,增加了维护成本,另增加了移植性的困难,据说squid的内存池是基于brk的,本人尚未阅读squid源码(了解磁盘缓存的最佳代码,以后再详细阅读),不敢妄言。本文后面的讨论的内存池都是基于malloc(或者new)实现。我们可以将内存池的实现分两个类别来讨论。
    1、不定长内存池。典型的实现有apr_pool、obstack。优点是不需要为不同的数据类型创建不同的内存池,缺点是造成分配出的内存不能回收到池中。这是由于这种方案以session为粒度,以业务处理的层次性为设计基础。
    (1)apr_pool。apr全称Apache portable Run-time libraries,Apache可移植运行库。可以从http://apr.apache.org/网站上下载到。apache以高性能、稳定性著称,它所有模块的内存申请都由内存池模块apr_pool实现。有关apr_pool结构、实现的原理,http://blog.csdn.net/tingya/(其中的apache内存池实现内幕系列)已经有了详细的讲解,结合自己下载的源码,已经足够了。本人并不推荐去看这个blog和去看详细的代码数据结构以及逻辑。明白apr_pool实现的原理,知道如何使用就足够了。深入细节只能是浪费脑细胞,当然完全凭个人兴趣爱好了。
    这里举例说下简单的使用:

#include "apr_pools.h"
#include <stdio.h>
#include <new>int main()
{apr_pool_t *root;//初始化全局分配子(allocator),并为它设置mutext,以用于多线程环境,//初始化全局池,指定全局分配子的owner是全局池apr_pool_initialize();//创建根池(默认父池是全局池),根池生命期为进程生存期//分配子默认为全局分配子apr_pool_create(&root,NULL);{apr_pool_t *child;apr_pool_create(&child,root);  //创建子池,指定父池为root。分配子默认为父池分配子void *pBuff=apr_palloc(child,sizeof(int));  //从子池分配内存//随便举例下:基于已分配内存后,面向对象构造函数的调用int *pInt=new (pBuff)  int(5);printf("pInt=%d\n",*pInt);{apr_pool_t *grandson;apr_pool_create(&grandson,root);void *pBuff2=apr_palloc(grandson,sizeof(int));int *pInt2=new (pBuff2)  int(15);printf("pInt2=%d\n",*pInt2);    apr_pool_destroy(grandson);}apr_pool_destroy(child);  //释放子池,将内存归还给分配子}apr_pool_destroy(root);  //释放父池apr_pool_terminate();    //释放全局池,释放全局allocator,将内存归还给系统return 1;
}

apr_pool中主要有3个对象,allocator、pool、block。pool从allocator申请内存,pool销毁的时候把内存归还allocator,allocator销毁的时候把内存归还给系统,allocator有一个owner成员,是一个pool对象,allocator的owner销毁的时候,allocator被销毁。在apr_pool中并无block这个单词出现,这里大家可以把从pool从申请的内存称为block,使用apr_palloc申请block,block只能被申请,没有释放函数,只能等pool销毁的时候才能把内存归还给allocator,用于allocator以后的pool再次申请。
     我给的例子中并没有出现创建allocator的函数,而是使用的默认全局allocator。apr_pool提供了一系列函数操作allocator,可以自己调用这些函数:

apr_allocator_create
apr_allocator_destroy
apr_allocator_alloc
apr_allocator_free
创建销毁allocator
apr_allocator_owner_set
apr_allocator_owner_get
设置获取owner
apr_allocator_max_free_set 设置pool销毁的时候内存是否直接归还到操作系统的阈值
apr_allocator_mutex_set
apr_allocator_mutex_get
设置获取mutex,用于多线程

另外还有设置清理函数啊等等,不说了。自己去看include里的头文件好了:apr_pool.h和apr_allocator.h两个。源码.c文件里,APR_DECLARE宏声明的函数即是暴露给外部使用的函数。大家也可以仿造Loki(后文将介绍Loki)写个顶层类重载operator new操作子,其中调用apr_palloc,使用到的数据结构继承该类,则自动从pool中申请内存,如要完善的地方很多,自行去研究吧。
     可以看出来apr_pool的一个大缺点就是从池中申请的内存不能归还给内存池,只能等pool销毁的时候才能归还。为了弥补这个缺点,apr_pool的实际使用中,可以申请拥有不同生命周期的内存池(类似与上面的例子程序中不同的大括号代表不同的生命周期,实际中,尽可以把大括号中的内容想象成不同的线程中的......),以便尽可能快的回收不再使用的内存。实际中apache也是这么做的。因此apr_pool比较适合用于内存使用的生命期有明显层次的情况。
     至于担心allocator中的内存一旦申请就再也不归还给操作系统(当然最后进程退出的时候你可以调用销毁allocator归还,实际中网络服务程序都是一直运行的,找不到销毁的时机)的问题,就是杞人忧天了,如果在某一时刻,系统占用的内存达到顶峰,意味着以后还会有这种情况。是否能接受这个解释,就看个人的看法和系统的业务需求了,不能接受,就使用其它的内存池。个人觉得apr_pool还是很不错的,很多服务系统的应用场景都适用。
    (2)obstack。glibc自带的内存池。原理与apr_pool相同。详细使用文档可以参阅 http://www.gnu.org/software/libc/manual/html_node/Obstacks.html。推荐apr_pool,这个就不再多说了。
    (3)AutoFreeAlloc。许式伟的博客实现的一个袖珍型垃圾回收器: http://blog.csdn.net/xushiweizh/article/details/1396573。这个内存池我不看好。这个也属于一个变长的内存池,内存申请类似与apr_pool的pool/block层面,一次申请大内存作为pool,用于block的申请,同样block不回收,等pool销毁的时候直接归还给操作系统。这个内存池的方案,有apr_pool中block不能回收到pool的缺点,没有pool回收到allocator,以供下次继续使用的优点,不支持多线程。适合于单线程,集中使用内存的场景,意义不是很大。

AutoFreeAlloc的发展方向应该就是apr_pool。apr_pool已经把变长的内存池发展到极致,当然这是当前看到的,或许以后有内存池会把变长内存池推到一个新的高度。:)
支持多线程的内存池都是从单线程加锁机制实现的,都提供无锁的实现。apr_pool也是,显式构造allocator后不调用apr_allocator_mutex_set就是无锁的实现。后面的boost和loki的无锁和有锁的实现区别更是明显。多线程的池子Lock-Free的解决办法很有前途。原子操作速度很快,没有锁的消耗。在C++标准库实现SGI STL中还有一种内存池的实现,就是用一系列固定长的内存池来实现一个不定长的内存池。它曾经是gcc3中stl的默认实现,但在gcc4中不再使用,理由是在多线程情况下优化并不明显,线程锁反而成为瓶颈。所以有时候一个线程一个内存池也是一个选择。

2、定长内存池。典型的实现有LOKI、BOOST。特点是为不同类型的数据结构分别创建内存池,需要内存的时候从相应的内存池中申请内存,优点是可以在使用完毕立即把内存归还池中,可以更为细粒度的控制内存块。与变长的相比,这种类型的内存池更加通用,另一方面对于大量不同的数据类型环境中,会浪费不少内存。但一般系统主要的数据结构都不会很多,并且都是重复申请释放使用,这种情况下,定长内存池的这点小缺点可以忽略了。
    (1)Loki::SmallObject系列

    Andrei Alexandrescu的《Modern C++ Design》第四章节已经进行了详细的描述,尽管和当前的loki版本实现有出入,还是了解Loki::SmallObject的最佳文字讲解,结合最新的loki源码,足够了。这里我再罗唆一下。先举例看下使用:

#include "loki/SmallObj.h"
class Small:public  Loki::SmallObject<>//继承SmallObject即可,所有都使用默认策略
{
public:Small(int data):data_(data){}
private:int data_;
};
int main()
{Small *obj=new Small(8);delete obj;
}

使用valgrind执行可以证实new一个obj和多new几次,申请的内存都是4192。可以看出loki在使用层面非常简单。
    loki的内存池分4层:从低向上依次是chunk、FixedAllocator、SmallObjAllocator、SmallObject。
    1)chunk:每个chunk管理一定数量(最大255,char型保存)的block,每个chunk中block的申请和释放,时间复杂度都是o(1),非常快,实现算法非常精巧,boost::pool中也是采用的相同算法。
    这里简单说下这个算法:首次申请一块连续内存,pdata_指向该内存基址,依据block大小,划分成多个连续的block,每个block开头的第一个字节保存该block的顺序号,第一个是1,第二个是2,依次类推。另有一字节变量firstAvailableBlock_存储上次分配出的block序号,开始是0。
    分配block:返回pdata_ +firstAvailableBlock_*blocksize,同时firstAvailableBlock_赋值为该块的序列号。
    回收block:block指针假设为pblock,该块序列号赋值为firstAvailableBlock_,firstAvailableBlock_赋值为(pblock-pdata_ )/blocksize即可。
    2)FixedAllocator:chunk中的block上限是255,不具有通用性,因此封装了一层,称为FixedAllocator,它保存了一个vector<chunk>,消除了单个chunk中block数目的上限限制。
    FixedAllocator中的block申请:FixedAllocator中保存活动的chunk(上次有空闲空间的chunk),申请block的时候如果活动chunk有空闲快,直接申请,否则扫描vector,时间复杂度o(N),同时更新活动chunk。
    FixedAllocator中的回收block:简单想,给定block回收到FixedAllocator,自然要扫描vector,以确认block属于哪个chunk,以便chunk回收。实际实现的时候,Loki针对应用场景进行了优化,一般使用都是批量使用,回收一般和申请顺序相同或者相反,因此FixedAllocator保存上次回收block的chunk指针,每次回收优先匹配这个chunk,匹配不上则以该chunk为中心,向两侧chunk顺序检测。
    FixedAllocator带来的优点:上文提到的消除了block的上限限制。另一方面,可以以chunk为单位,把内存归还给操作系统。实际实现中防止刚释放的内存立即又被申请,是存在两个空闲chunk的时候才回收一个。这个特点,这里暂时归结为优点吧。实际使用中,回收多余内存个人认为是个缺点,意义并不是很大。
    FixedAllocator带来的缺点:很明显,就是申请回收block的时间复杂度。
    3)SmallObjAllocator:截至到FixedAllocator层面blocksize都是定长。因此封装一层适用于任意长度的内存申请。SmallObjAllocator保存了一个FixedAllocator的数组pool_,存储拥有不同block长度的FixedAllocator。《Modern C++ Design》中描述该数组下标和存储的FixedAllocator的block长度无直接关系,从SmallObjAllocator申请以及回收block的时候二分查找找到对应的FixedAllocator再调用相应FixedAllocator的申请或者回收。当前最新版本的loki,已经抛弃了这种做法。当前SmallObjAllocator的构造函数有3个参数:chunksize,maxblocksize,alignsize。数组元素个数取maxblocksize除以alignsize的向上取整。每个FixedAllocator中实际的blocksize是(下标+1)*alignsize。
    SmallObjAllocator中block申请:依据block和alignsize的商直接取到数组pool_下标,使用相应的FixedAllocator申请。
    SmallObjAllocator中回收block:根据block和alignsize的商直接找到相应的FixedAllocator回收。
    优点:差异化各种长度的对象申请,增强了易用性。
    缺点:《Modern C++ Design》中描述增加扫描的时间复杂度,当前版本的loki浪费内存。这也是进一步封装,屏蔽定长申请的细节,带来的负面效应。
    4)SmallObject。
暴露给外部使用的一层。该层面秉承了《Modern C++ Design》开始引入的以设计策略类为最终目的,让用户在编译期选择设计策略,而不是提供框架限制用户的设计。这也是引入模板的一个层面。当前版本SmallObject有6个模版参数,第一个是线程策略,紧接着的三个正好是SmallObjAllocator层面的三个构造参数,下面的一个生存期策略,最后的是锁方式。
    这里说下SmallObjAllocator层面的三个默认参数值,分别是4096,256,4。意味着SmallObjAllocator层面有数组(256+4-1)/4=64个,数组存储的FixedAllocator中的chunksize一般都是4096(当4096<=blocksize*255时候)字节(第一个chunk的申请推迟到首次使用的时候),各FixedAllocator中的chunk的blocksize依次是4、8......256,大于256字节的内存申请交给系统的malooc/new管理,数组中FixedAllocator中单个chunk中的blocknum依次是4096/4=824>255取255、255......4096/256=16。如果这不能满足需求,请调用的时候显式赋值。
    当前loki提供了三种线程策略:

SingleThreaded 单线程
ObjectLevelLockable 对象级别,一个对象一个锁
ClassLevelLockable 类级别,一个类一个锁,该类的所有对象共用该锁

目前只提供了一种锁机制:Mutex
    它的基类SmallObjectBase复写了new/delete操作子,因此直接继承SmallObject就可以象普通的类一样new/delete,并且从内存池分配内存。
    SmalObject中block申请和释放都从一个全局的SmallObjAllocator单例进行。
    评价:chunk层面限制了上限个数,导致了FixedAllocator层面出现,造成申请回收时间复杂度的提高,而以chunk为单位回收内存,在内存池的使用场景下意义并不是很大。SmallObjAllocator为了差异化变长内存的申请,对FixedAllocator进一步封装,引入了内存的浪费,不如去掉这个层面,直接提供给用户层面定长的接口。另一方面,loki已经进行了不少优化,尽可能让block申请释放的时间复杂度在绝大多数情况下都是O(1),而SmallObjAllocator中内存的浪费可以根据alignsize调整,即便是极端情况下,loki将chunk归还给系统又被申请出来,根据chunk中block的最大值看,也比不使用内存池的情况动态申请释放内存的次数减少了1/255。因此,loki是一个非常不错的小巧的内存池。

(2)boost::pool系列

    boost的内存池最低层是simple_segregated_storage,类似于Loki中的chunk,在其中申请释放block(boost中把block称为chunk,晕死,这里还是称其为block)采用了和loki的chunk中同样的算法,不同的是simple_segregated_storage使用void*保存block的块序号,loki中使用char,因此boost中的simple_segregated_storage没有255的上限限制,自然也就不需要再其上再封装一层类似与FixedAllocator的层面。另boost没有屏蔽块的大小,直接提供定长的接口给用户,省掉了SmallObjAllocator层面。因此boost的内存池申请释放block的时间复杂度都是O(1)(object_pool和pool_allocator除外),另外为了避免小内存的浪费,同时boost不能象loki那样在将block归还给内存池时,根据chunk的空闲数量释放内存归还给系统,只能显式调用释放内存函数或者等内存池销毁的时候,基本上和内存池生命周期内永不释放没什么区别。
    boost的最低层是simple_segregated_storage,主要算法和loki中的chunk一样,不多说了。这里说下影响上层接口的两类实现:add_block/malloc/free、add_ordered_block/malloc/ordered_free,两种低层实现造成boost上层设计的成功与失败,前者效率高,和loki一样直接增加释放,时间复杂度O(1),后者扫描排序,时间复杂度O(n)。
     boost提供了四种内存池模型供使用:pool、object_pool、singleton_pool、pool_allocator/fast_pool_allocator。
    1)pool:基本的定长内存池。

#include <boost/pool/pool.hpp>
typedef struct student_st
{char name[10];int age;
}CStudent;
int main()
{boost::pool<> student_pool(sizeof(CStudent));CStudent * const obj=(CStudent *)student_pool.malloc();student_pool.free(obj);return 0;
}

pool的模板参数只有一个分配子类型,boost提供了两种default_user_allocator_new_delete/default_user_allocator_malloc_free,指明申请释放内存的时候使用new/delete,还是malloc/free,默认是default_user_allocator_new_delete。构造函数有2个参数:nrequested_size,nnext_size。nrequested_size是block的大小(因为void*保存序号,因此boost内置了block的最小值,nrequested_size过小则取内置值),nnext_size是simple_segregated_storage中内存不足的时候,申请的block数量,默认是32。最全面的实例化pool类似这样:

boost::pool<boost::default_user_allocator_malloc_free> student_pool(sizeof(CStudent), 255);
    pool提供的函数主要有:

malloc/free  基于add_block/malloc/free实现,高效
ordered_malloc/ordered_free 基于add_ordered_block/malloc/ordered_free实现,在pool中无任何意义,切勿使用。
release_memory/purge_memory 前者释放池中未使用内存,后者释放池中所有内存。另池析构也会释放内存

2)object_pool:对象内存池,这是最失败的一个内存池设计。

#include <boost/pool/object_pool.hpp>class A{
public:A():data_(0){}
private:int data_;
};
int main()
{boost::object_pool<A> obj_pool;A *const pA=obj_pool.construct();obj_pool.destroy(pA);return 0;
}

object_pool继承至pool,有两个模板参数,第一个就是对象类型,第二个是分配子类型,默认同pool是default_user_allocator_new_delete。构造函数参数只有nnext_size,意义以及默认值同pool。最全面的实例化object_pool类似这样:

boost::pool<A,boost::default_user_allocator_malloc_free> obj_pool(255);
    object_pool提供的函数主要有(继承至父类的略):

malloc/free 复写pool的malloc/free,add_ordered_block/malloc/ordered_free实现
construct/destroy 基于本类的malloc/free实现,额外调用默认构造函数和默认析构函数。
~object_pool 单独拿出这个说下,若析构的时候有对象未被destroy,可以检测到,释放内存前对其执行destroy

为什么boost::object_pool要设计成这样?通过construct/destroy来调用构造函数和析构函数显然不是boost::object_pool类设计的出发点,它的重点是内存释放时候的清理工作,这个工作默认的析构函数就足够了。apr_pool内存池中就可以注册内存清理函数,在释放内存的时刻执行关闭文件描述符、关闭socket等操作。但是boost::object_pool也想实现同样的功能,因此设计了destroy这个函数。为了防止用户遗漏掉这个调用,必须在内存池析构的时候进行回收的检测。通常这会使得析构object_pool的时间复杂度是O(n平方),为了减少时间复杂度,boost::object_pool付出了沉重的代价,在每次的destoy都执行排序功能,时间复杂度O(n),最后析构的时间复杂度是O(n)。同样为了这个目的,从simple_segregated_storage增加了add_ordered_block/ordered_free,pool增加了ordered_malloc/ordered_free等累赘多余的功能。
     基于上面讨论的原因,boost::object_pool被设计成了现在的样子,成了一个鸡肋类。 类的设计者似乎忘记了内存池使用的初衷,忘记了内存池中内存申请释放的频率很高,远远大于内存池对象的析构。如果你依然想使用类似于此的内存清理功能,可以在boost::object_pool上修改,不复写malloc/free,而是重写object_pool的析构,简单释放内存就好。因此析构object_pool前不要忘记调用destroy,这也是使用placement new默认遵守的规则,或者保持以前的析构函数,牺牲析构时的性能。placement new的作用是为已经申请好的内存调用构造函数,使用流程为:

(1)申请内存buf

(2)调用placement new:new(buf) construtor()

(3)调用析构destructor()

(4)释放内存buf。#include<new>可以使用placement new。
    3)singleton_pool:pool的加锁版本。

#include <boost/pool/singleton_pool.hpp>
typedef struct student_st
{char name[10];int age;
}CStudent;
typedef struct singleton_pool_tag{}singleton_pool_tag;
int main()
{typedef boost::singleton_pool<singleton_pool_tag,sizeof(CStudent)>  global;CStudent * const df=(CStudent *)global::malloc();global::free(df);return 0;
}

singleton_pool为单例类,是对pool的加锁封装,适用于多线程环境,其中所有函数都是静态类型。它的模板参数有5个。

tag:标记而已,无意义;

RequestedSize:block的长度;

UserAllocator:分配子,默认还是default_user_allocator_new_delete;

Mutex:锁机制,默认值最终依赖于系统环境,linux下是pthread_mutex,它是对pthread_mutex_t的封装;

NextSize:内存不足的时候,申请的block数量,默认是32。

最全面的使用singleton_pool类似这样:

typedef boost::singleton_pool<singleton_pool_tag, sizeof(CStudent), default_user_allocator_new_delete, details::pool::default_mutex, 200> global;
    它暴露的函数和pool相同。
    4)pool_allocator/fast_pool_allocator
    stl::allocator的替换方案。两者都是基于singleton_pool实现,实现了stl::allocator要求的接口规范。两者的使用相同,区别在于pool_allocator的实现调用ordered_malloc/ordered_free,fast_pool_allocator的实现调用malloc/free,因此推荐使用后者。

#include <boost/pool/pool_alloc.hpp>
#include <vector>
typedef struct student_st
{char name[10];int age;
}CStudent;int main()
{std::vector<CStudent *,boost::fast_pool_allocator<CStudent *> > v(8);CStudent *pObj=new CStudent();v[1]=pObj;boost::singleton_pool<boost::fast_pool_allocator_tag,sizeof(CStudent *)>::purge_memory(); return 0;
}

fast_pool_allocator的模板参数有四个:类型,分配子,锁类型,内存不足时的申请的block数量,后三者都有默认值,不再说了。它使用的singleton_pool的tag是boost::fast_pool_allocator_tag。
    评价:boost::pool小巧高效,多多使用,多线程环境下使用boost::singleton_pool,不要使用两者的ordered_malloc/ordered_free函数。boost::object_pool不建议使用,可以改造后使用。pool_allocator/fast_pool_allocator推荐使用后者。

附:关于ordered_malloc和ordered_free的再探讨

我觉得boost::pool的ordered_malloc和ordered_free函数是有必要的,它们是作为ordered pool的接口部分而存在的,使用场景不一样,和malloc和free是没有可比性。
    boost::pool中有一个函数malloc_n用来申请几块连续的partition。一个block是以partition为粒度划分,以单向链表链接起来,多次malloc/free的动作之后,这些partition在链表中的逻辑顺序和它们在内存中的实际顺序极有可能是不一致的,此时要得到连续的partition,必须要遍历链表,找出在内存中实际连续的partition,并将它们在链表中的顺序也重新以连续的顺序链接,然后返回给调用者。
    如果将链表的顺序与内存中实际顺序的不一致程度叫做它的无序度,显然这个遍历操作的耗费的时间,和链表的无序度是成正比的。如果这种malloc_n的操作很频繁,那么这种遍历链表重新链接的操作会很费时。
    这种频繁申请多个连续partition的场景下正是使用ordered pool的地方,正如pool作者在代码写的注释那样:
// Note: if you're allocating/deallocating n a lot, you should
// be using an ordered pool.
    ordered pool内部的所有partition链表无序度为0,即所有partition在内存中的实际顺序和它们在链表中的逻辑顺序完全一致,对于malloc_n这样的请求,可以更快满足。也因此boost::pool提供了ordered版本的操作接口,这组接口保证每次free之后链表的顺序不会乱。
    不过,我个人觉得block以partition为粒度划分是没必要的,可以在收到请求的时候按实际需求划分,尽力使整个块保持连续,可以极大地避免产生碎片,降低ordered_malloc的ordered_free的时间。
    当然ordered_xxx函数不应该和其它的函数混用,否则就是陪了夫人又折兵的买卖,即浪费了ordered_xxx的时间,又无法得到更快malloc_n的好处。

参考文献:

技术系列之内存管理:http://www.cppblog.com/CppExplore/MyPosts.html

转载于:https://my.oschina.net/abcijkxyz/blog/723278

服务器设计系列:内存管理相关推荐

  1. ARM和英特尔的386系列内存管理MMU硬件机制不同,ARM是基于协处理CP15(核心是C2 TTRB0,1)分页式,386是以段寄存器和CR3寄存器分段分页式内存管理

    ARM和英特尔的386系列内存管理MMU硬件机制不同,ARM是基于协处理CP15(核心是C2 TTRB0,1)分页式,386是以段寄存器和CR3寄存器分段分页式内存管理

  2. java内存管理课程设计_Java内存管理分析

    Java内存主要分为stack, heap, data segment, and code segment. stack(栈):存放非静态基本数据类型变量的名称和值,以及非静态对象的引用 若是非静态基 ...

  3. linux c 指针和内存分配内存,linux-C基础系列-内存管理(野指针).md

    野指针概述 野指针通常指的是指针变量中保存的值不会死一个合法的内存地址,但又对其访问.需要注意的是野指针不是空指针,而是指向内存不可用的指针. C语言中对于空指针(NULL)是可以判断出来的,但是野指 ...

  4. 服务器设计系列 网络模型,网络服务器的结构模型

    网络服务器的结构模型 内容精选 换一换 模型.数据集.校准集的准备动作请参见量化前提.量化因子:以昇腾模型压缩工具的安装用户将用户自己计算得到的量化因子记录文件上传到Linux服务器任意目录下.本手册 ...

  5. 专为Windows电脑和服务器设计的磁盘管理软件

    关于Windows磁盘管理   磁盘管理是Windows自带工具,允许你对磁盘进行一些基本操作,Windows个人用户和Windows Server用户可以使用它来: 1. 创建一个新驱动器,如&qu ...

  6. 【redismemcached】数据类型、内存管理、数据持久化和集群管理的区别

    这几年redis很火,redis也常常被当做memcached的挑战者被提到桌面上来.关于redis和memcached的比较比比皆是.然而,redis真的在功能.性能以及内存使用效率上都超越memc ...

  7. 【C++内存管理侯捷】---学习笔记(下)malloc/free,loki allocator,other issue

    第三讲 malloc/free 3.1 VC6和VC10的malloc比较 malloc/free是C层面的函数 源代码是来自于VC6.0:因为比较复杂,因此以图为主,辅以部分源代码,理解其运行机制: ...

  8. Redis和Memcached的区别(数据类型、内存管理、数据持久化、集群管理)

    Redis的作者Salvatore Sanfilippo曾经对这两种基于内存的数据存储系统进行过比较: Redis支持服务器端的数据操作:Redis相比Memcached来说,拥有更多的数据结构和并支 ...

  9. (六)Linux内存管理 - zoned page frame allocator - 1

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

最新文章

  1. ios 处理WKContentView的crash
  2. 高级考题_经济师考过就满足?还有这个高级职称等你拿!
  3. JoinGroup的过程
  4. ch4 MySQL 安全管理
  5. Exchange2010安装过程中先决条件报错得处理方法
  6. poj 1905 Expanding Rods 二分
  7. SQL Sever 常用语句总结
  8. 11 旋转数组的最小数字
  9. 在vue中实现锚点定位功能
  10. MS08067 第一期 “恶意代码分析”实战班 12.17号开班~
  11. C4D R26 安装教程
  12. matlab gpu加速,Matlab之GPU加速方法
  13. android前置摄像头预览,android - 当选择了前置摄像头CameraX预览没有显示任何东西 - 堆栈内存溢出...
  14. 【知识图谱】实践篇——基于知识图谱的《红楼梦》人物关系可视化及问答系统实践:part1项目介绍与环境准备
  15. 3D房地产营销PPT模板
  16. 什么是在制品限制?通过这篇文章,让你搞明白
  17. 你真的会用区块链赚钱吗?论区块链的商业思维
  18. gfsj (logmein)
  19. 高级语言与低级语言的区别
  20. 如何利用开关量信号传输装置实现工厂智能化技改?

热门文章

  1. android标题栏添加按钮_2019最新Android常用开源库总结(持续更新,建议收藏)
  2. 【转载】尼康D90购机指南
  3. 大数据【企业级360°全方位用户画像】匹配型标签开发
  4. 01-【Xshell】Xshell如何修改字体大小和颜色
  5. Vue生命周期中钩子函数理解简记
  6. 海南发布第一批共享数据目录清单,共编录50家省级政府单位的29431项数据项
  7. Replica Island 学习笔记 03 - Activity分析
  8. @Import和spring.factories实现原理
  9. linux排序格式,LINUX SORT 语法格式
  10. join ... on.... [and] 的理解