[原创]收集专用高效内存池,支撑多线程.原创,非sgi的内存池

    添加时间:2013-5-28 点击量:

      起首要申明一点,这个内存池应用时须要重视的,若是想用在长久分派出去的内存,请慎用.
      因为假如一个区块被分派完了,只有在这个区块里已分派的内存被完全开释后,这个区块才干重用.


    因为当初是设计为收集分包用的内存池.为了效力而采取这个策略的.


      发代码之前先简单介绍下内存池的思路.
      内存池分256个区块,编号为0~255
    区块的布局为:


    区块记录了3个信息一个指针


    _left_mem是残剩的memory,初始化为区块内存总长


    _alloc_org是指向区块有效内存的肇端点.初始化为0(全部区块一开端都是可以有效分派出去的)


    _need_to_rel是须要开释的内存.初始化为区块内存总长
    内存块先简单介绍.后面再具体说实现细节



    //区块,用于保存未分派内存(被分别出去)
    
    struct _chunk{
    int _left_mem; //此chunk残剩长度
    int _alloc_org; //指向可用于分派内存快头的指针
    int _need_to_rel;//须要开释几许内存

    char _mem[1]; //此chunk持有的内存区
    };


      而分派出去的块用以下布局标识


      



    struct _mem_piece{    
    

    int _mp_len; //内存块大小
    char _chunk_index; //内存块所属区块的索引

    char _mem[1]; //内存块的内存首地址
    };




      内存池的所有区块可以分为三个区域


      无效区:这里的区块已经完全分派完毕,守候开释,开释完成后会到区


      待分派区:这个区有且只有一个块,分派内存都邑从这里抽取


      区:


      下图是内存池的状况:
      
    内存池一开端会处于以下的状况

    初始化的时辰第一个区块会被初始化.持续分派就会耗尽绿色的待分派块,内存池会把它归入无效区,同时测验测验从体系创建一个新的区块并列为待分派块
    只有无效区的内存的块被完全开释后才会转到.当然,若是有区块的话,就不须要新建区块,而是直接指定的第一个区块为待分派区块

      然后我再来申明一下分派的策略._left_mem一开端就是该区块总内存大小这个不须要多说了.
      malloc被调用时:
      1.当_left_mem比请求分派的内存多的时辰.把当前_alloc_org处罚配出去,削减_left_mem和移动_alloc_org.


      2.a.当_left_mem不足的时辰,_left_mem(剩下的内存)将成为碎片,策画need_to_rel -= _left_mem,此区块将移动到无效区,并且内存池将获取一个新的区块(若是有则直接获取,不然创建一个)


      分派出去的时辰都履行以下的代码:


      则实际上须要的内存大小是sizeof(int) + sizeof(char) + memory size
    而客户所获得的内存是从mp->_mem开端的.所以,free回来的内存只要向前移动5个字节就能获取到分派出去的_mem_piece布局了



           _mem_piece mp = (_mem_piece )&p->_mem[p->_alloc_org];
    
           
    //内存块真正的大小
    mp->_mp_len = need_len;
    //chunk标识
    mp->_chunk_index = _S_cur_valid_index;

    return mp->_mem;



       b.策画need_to_rel -= _left_mem后发明need_to_rel==0则不是移动到无效区而是直接移动到.


      free被调用时:
        _need_to_rel-=被开释的内存大小,发明_need_to_rel == 0则直接把此块移动到区 


        free调用的时辰是如许获取到回来的内存大小的


        



    _mem_piece pmem = (_mem_piece )((char )p - _mem_piece::_mem_piece_struct_size);


        _mem_piece记录了这块被分派的内存有多大.直接经由过程pmem->_mp_len获取,pmem->_chunk_index则记录了属于哪个区块的,如许便可以直接归并回该区块了.


       shrink被调用时:


        把所有处于的区块开释,调用c标准库的free把内存还给体系,阁下瘦身收敛的结果.


      _need_to_rel仅仅是一个标识表记标帜.在调用free的时辰若是发明_need_to_rel==0了就直接把这个区块丢到里面.因为终极分派完这个区块今后须要开释的值应当为 区块总内存 - 残剩碎片.所以_need_to_rel一开端就是区块总内存了.在方才绿色的待分派被完全耗尽今后,在转入无效区之前,管帐算出碎片大小(就是_left_mem)这个时辰履行need_to_rel -= _left_mem策画出真实的需开释值.策画完后,法度若是发明need_to_rel==0不会归入无效区而是直接转到


      最后总结下这个内存池的优毛病:


      长处:


        1.分派的时辰仅仅移动了指针,内存不足时(即创建新块的时辰)调用到c运行库的malloc.若是不算上malloc的话.内存池分派只须要履行少于10个指令.
        所以分派速度很是快


        2.开释的时辰不须要归并这个步调.很多内存分派器都是在归并上用了很多时候.这个内存池不须要任何归并的动作


        3.可以分派随便率性大小的内存.当然,太大的时辰会委托给malloc


      毛病:


         1.若是分派完一个区块后会造成少量碎片


       2.无效区域内的内存只有等完全开释了,这个块才可以重用,所以不合适长久占领内存池的内存.


               这个很大限度限制了这个内存池的实用性.


       然后贴个测试:



    #include fish_mem_pool.h
    
    #include
    <boost/timer.hpp>
    #include
    <iostream>
    int main()
    {
    boost::timer t;
    forint i = 0; i < 1024 600; ++i)
    char p = (char )fishlib::fish_mem_pool::malloc(1024);

    std::cout
    << used << t.elapsed() << seconds << std::endl;

    getchar();
    return 0;
    }


      成果:


      


      



      最后上代码:


      设备内存池的头文件:


    重视!内存池最大内存大小为256 _new_chunk_len


    这里为 256  4096000 = 1000MB


    fish_mem_pool_config.h




    / @fish_mem_pool_config.h        ----------------------------------------------
    
    Copyright (c) TCC 2013
    Project :
    fish & Date : 2012/09/06
    Files :
    Brief : 按照列举值,和宏定义改变fish_mem_pool的设备
    Update :
    Note :
    -----------------------------------------------------------------
    /

    #ifndef _FISHLIB_FISH_MEM_POOL_CONFIG_H_
    #define _FISHLIB_FISH_MEM_POOL_CONFIG_H_

    #define _MULTI_THREAD_ //定义多线程景象


    //这里是采取了boost做多线程同步,可以自行更改
    //然则必须合适lock和unlock的接口
    //多线程宏定义
    #ifdef _MULTI_THREAD_
    #include
    <boost/thread/mutex.hpp>
    //#include <boost/signals2/mutex.hpp>
    typedef boost::mutex _LOCK_CORE;
    #endif //_MULTI_THREAD_


    namespace fishlib{
    enum _config_val{
    _max_len
    = 8192//内存池能分派的最大内存数,大于这个数的全都应用malloc

    _new_chunk_len
    = 4096000 //新chunk的长度
    //因为只有256个区块,所以内存池最多能分派_new_chunk_len 256的内存
    //请衡量_max_len / _new_chunk_len的值,这个值越大则一个chunk的哄骗率可能会降落
    //_max_len / _new_chunk_len越小,则chunk的哄骗率越高
    //尽量对峙在1/2以下
    };
    }

    #endif //_FISHLIB_FISH_MEM_POOL_CONFIG_H_





      内存池的声明头文件:


    fish_mem_pool.h



    / @fish_mem_pool.h        ----------------------------------------------
    
    Copyright (c) TCC 2013
    Project :
    fish & Date : 2012/09/06
    Files :
    Brief :
    Update :
    Note :
    -----------------------------------------------------------------
    /
    #ifndef _FISHLIB_FISH_MEM_POOL_H_
    #define _FISHLIB_FISH_MEM_POOL_H_


    namespace fishlib{

    class fish_mem_pool{
    public:
    /
    大于_max_len的直接用malloc分派,分派失败返回NULL
    /
    static void malloc(int len);

    /
    由fishlib::fish_mem_pool::malloc分派的都要用这个函数开释
    /
    static void free(void p);

    /
    手动紧缩,调用后内存池会把余暇块还给体系,返回开释了几许内存
    /
    static int shrink();
    };
    }

    #endif //_FISHLIB_FISH_MEM_POOL_H_



     内存池实现文件:


    fish_mem_pool.cpp



    / @fish_mem_pool.cpp        ----------------------------------------------
    
    Copyright (c) TCC 2013
    Project :
    fish & Date : 2012/09/06
    Files :
    Brief :
    Update :
    Note :
    -----------------------------------------------------------------
    /
    #include
    fish_mem_pool.h
    #ifndef _FISHLIB_FISH_MEM_POOL_CONFIG_H_
    #include
    fish_mem_pool_config.h
    #endif //_FISHLIB_FISH_MEM_POOL_CONFIG_H_
    #include
    <stdlib.h>
    #include
    <memory.h>
    #include
    <assert.h>


    //模板类,益处是若是没有效到则不会生成,甚至不会检测语法错误
    template<typename T>
    class _MEM_POOL_LOCK{
    public:
    //主动加锁
    _MEM_POOL_LOCK(T &t_lock): _t_lock(t_lock){
    _t_lock.
    lock();
    }
    //主动在跳出感化域时解锁
    ~_MEM_POOL_LOCK(){
    _t_lock.unlock();
    }
    private:
    T
    &_t_lock;
    };


    //多线程宏定义
    #ifdef _MULTI_THREAD_

    static _LOCK_CORE _S_lc;
    #define FISH_LOCK _MEM_POOL_LOCK<_LOCK_CORE> mpl(_S_lc);

    #ifdef _DEBUG
    static _LOCK_CORE _S_dbg_lc;
    #define FISH_DBG_LOCK _MEM_POOL_LOCK<_LOCK_CORE> mpl(_S_dbg_lc);
    #endif //_DEBUG

    #else
    //在非多线程状况下,此宏定义不会做任何工作
    #define FISH_LOCK

    #ifdef _DEBUG
    #define FISH_DBG_LOCK
    #endif //_DEBUG

    #endif //_MULTI_THREAD_

    namespace fishlib{

    enum default_val{
    _max_chunk_cnt
    = 256 //最大区块数,默认值,只能是小于256的数字
    };


    //内存池全局变量
    //记录内存池状况
    struct _chunk;
    static _chunk _S_chunk_list[_max_chunk_cnt]; //区块表,记录所有区块
    static unsigned char _S_cur_valid_index = 0; //当前指向的有效区块,有效区块有且只有一个
    static unsigned char _S_invalid_index_s = 0; //第一个无效的区块
    static unsigned char _S_invalid_cnt = 0; //无效区块总数
    static unsigned char _S_free_index_s = 0; //区开端索引

    static int _S_total_chunk_cnt = 0; //统共的区块数



    //////////////////////////////////////////////////////////////////////////

    //内存池内部应用布局定义


    #pragma pack(1)

    //区块,用于保存未分派内存(被分别出去)
    struct _chunk{

    enum{
    _chunk_struct_size
    = sizeofint) + sizeofint) + sizeofint
    };

    int _left_mem; //此chunk残剩长度
    int _alloc_org; //指向可用于分派内存快头的指针
    int _need_to_rel;//须要开释几许内存

    char _mem[1]; //此chunk持有的内存区

    inline
    static _chunk new_chunk(){
    if (_S_total_chunk_cnt < _max_chunk_cnt){
    //若是总区块数小于最大限制则可以分派
    _chunk p = (_chunk )::malloc(_chunk_struct_size + _new_chunk_len);

    if (p == NULL)
    //体系内存不足
    return NULL;

    //终极分派成功
    ++_S_total_chunk_cnt;
    p
    ->_alloc_org = 0;
    p
    ->_left_mem = _new_chunk_len;
    p
    ->_need_to_rel = _new_chunk_len;
    return p;
    }
    else{
    return NULL;
    }
    }

    inline
    static void _chunk(_chunk p){
    //一个chunk烧毁,并且把内存交还给体系
    --_S_total_chunk_cnt;
    ::free(p);
    }
    };

    //内存块,用于记录已分派内存
    struct _mem_piece{
    enum{
    _mem_piece_struct_size
    = sizeofchar) + sizeofint
    };

    int _mp_len; //内存块大小
    char _chunk_index; //内存块所属区块的索引

    char _mem[1]; //内存块的内存首地址
    };
    #pragma pack()

    //////////////////////////////////////////////////////////////////////////

    //用于内存池主动初始化的布局
    struct _init_mem_pool{
    _init_mem_pool(){
    //内存池初始化代码
    memset(_S_chunk_list, 0, _max_chunk_cnt sizeof(_chunk )); //清零
    _S_chunk_list[0] = _chunk::new_chunk();
    _S_cur_valid_index
    = 0;
    _S_invalid_index_s
    = 0;
    _S_invalid_cnt
    = 0;
    }
    ~_init_mem_pool(){

    }
    };

    //自消息态对象,在此调用初始化代码
    static _init_mem_pool imp;


    //助手函数
    //从一个chunk中获取内存
    static inline void _get_mem(int need_len){
    //取出当前有效chunk
    _chunk p = _S_chunk_list[_S_cur_valid_index];

    if (p->_left_mem <= need_len){
    //内存不足
    //因为在残剩空间刚巧便是的时辰景象斗劲错杂,故舍弃这种可能
    return NULL;
    }
    else{

    _mem_piece
    mp = (_mem_piece )&p->_mem[p->_alloc_org];

    //抽取内存
    //残剩内存较少
    //指向可用于分派内存快头的指针增长
    p->_left_mem -= need_len;
    p
    ->_alloc_org += need_len;


    //内存块真正的大小
    mp->_mp_len = need_len;
    //chunk标识
    mp->_chunk_index = _S_cur_valid_index;

    return mp->_mem;
    }
    }

    //获得区块总长
    static inline int _get_free_chunk_cnt(){
    return _S_total_chunk_cnt - (_S_invalid_cnt + 1);
    }

    //增长_S_cur_valid_index
    static inline unsigned char _inc_cur_idx(unsigned char idx){
    ++idx;

    if (_S_chunk_list[idx] == NULL){
    idx
    -= _S_total_chunk_cnt;
    }

    return idx;
    }

    //使开释无效区块,成为区块
    static inline void _free_invalid_chunk(unsigned char index){
    //把要成为chunk的区块变为初始状况
    _S_chunk_list[index]->_alloc_org = 0;
    _S_chunk_list[index]
    ->_left_mem = _new_chunk_len;
    _S_chunk_list[index]
    ->_need_to_rel = _new_chunk_len;

    //与第一个无效区块互换,再++_S_invalid_index_s使其挪出无效区域
    _chunk ptmp = _S_chunk_list[index];
    _S_chunk_list[index]
    = _S_chunk_list[_S_invalid_index_s];
    _S_chunk_list[_S_invalid_index_s]
    = ptmp;

    _S_invalid_index_s
    = _inc_cur_idx(_S_invalid_index_s);
    --_S_invalid_cnt;
    }


    //真正移动块
    static inline void _in_next_chunk(){
    //须要开释的内存 - 残剩内存,意义:残剩的内存不须要开释
    unsigned char index = _S_cur_valid_index;
    _chunk
    p = _S_chunk_list[index];

    int need_to_rel = p->_need_to_rel;
    need_to_rel
    -= p->_left_mem;
    p
    ->_need_to_rel = need_to_rel;

    //移动到下一个
    _S_cur_valid_index = _inc_cur_idx(_S_cur_valid_index);
    //扩充无效区域
    ++_S_invalid_cnt;


    assert(p
    ->_need_to_rel >= 0 && p->_need_to_rel <= _new_chunk_len);

    if (p->_need_to_rel == 0){
    //若是内存块其他分派出去的都被用完了
    //那就让它直接成为chunk
    _free_invalid_chunk(index);
    }
    }

    //测验测验移动区块
    //失败返回false
    static inline bool _next_chunk(){
    if (_get_free_chunk_cnt() == 0){
    //区耗尽,须要抽取内存
    if (_S_total_chunk_cnt >= _max_chunk_cnt){
    //无法抽取函数,池已满
    return false;
    }
    else{
    int idx = _S_cur_valid_index + 1;
    _chunk
    p = _chunk::new_chunk();
    if (p == NULL){
    //体系内存已经耗尽了
    return false;
    }
    else{
    //初始化新地位
    _S_chunk_list[idx] = p;
    //移动到新地位
    _in_next_chunk();
    return true;
    }
    }
    }
    else{
    //应用块
    _in_next_chunk();
    _S_free_index_s
    = _inc_cur_idx(_S_free_index_s);
    return true;
    }
    }


    //内存不足返回NULL
    void fish_mem_pool::malloc(int ilen){
    assert(ilen
    > 0);
    int real_need_len = ilen + _mem_piece::_mem_piece_struct_size;

    if (real_need_len > _max_len){
    //请求的内存太大
    //委托给malloc
    _mem_piece p = (_mem_piece )::malloc(real_need_len);
    if (p == NULL)
    return NULL;
    p
    ->_chunk_index = 0;
    p
    ->_mp_len = real_need_len;
    return p->_mem;
    }
    else{
    //加锁
    FISH_LOCK

    void p = _get_mem(real_need_len);
    if (p == NULL){
    //当前chunk的内存已经耗尽,测验测验移动到下一个chunk
    bool succeed = _next_chunk();
    if (succeed){
    return _get_mem(real_need_len);
    }
    else{
    return NULL;
    }
    }
    else{
    return p;
    }
    }
    }

    void fish_mem_pool::free(void p){
    _mem_piece
    pmem = (_mem_piece )((char )p - _mem_piece::_mem_piece_struct_size);
    if (pmem->_mp_len <= _max_len){
    //加锁
    FISH_LOCK

    unsigned
    char cindex = pmem->_chunk_index;
    _S_chunk_list[cindex]
    ->_need_to_rel -= pmem->_mp_len;
    if (_S_chunk_list[cindex]->_need_to_rel == 0){
    //发明须要开释的内存已经开释完,则让chunk变为chunk
    _free_invalid_chunk(cindex);
    }
    }
    else{
    //内存块太大,是委托给malloc的内存块
    ::free(pmem);
    }
    }

    int fish_mem_pool::shrink(){

    //加锁
    FISH_LOCK

    int free_cnt = _get_free_chunk_cnt();
    int cnt = 0;
    unsigned
    char idx = 0;
    while (cnt != free_cnt){
    if (_S_chunk_list[idx] != NULL){
    if (_S_chunk_list[idx]->_left_mem == _new_chunk_len){
    _chunk::_chunk(_S_chunk_list[idx]);
    _S_chunk_list[idx]
    = NULL;
    ++cnt;
    }
    }
    ++idx;
    }


    return (free_cnt (_chunk::_chunk_struct_size + _new_chunk_len));
    }

    }




      


      
      

    真正的心灵世界会告诉你根本看不见的东西,这东西需要你付出思想和灵魂的劳动去获取,然后它会照亮你的生命,永远照亮你的生命。——王安忆《小说家的十三堂课》
    分享到: