网络专用高效内存池,支持多线程.原创,非sgi的内存池

来源:互联网 发布:java scanner读取char 编辑:程序博客网 时间:2024/06/02 06:30
首先要说明一点,这个内存池使用时需要注意的,如果想用在长期分配出去的内存,请慎用.
  因为假如一个区块被分配完了,只有在这个区块里已分配的内存被完全释放后,这个区块才能重用.

因为当初是设计为网络分包用的内存池.为了效率而采用这个策略的.

  发代码之前先简单介绍下内存池的思路.
  内存池分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;    for (int 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 _DEBUGstatic _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 = sizeof(int) + sizeof(int) + sizeof(int)        };        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 delete_chunk(_chunk *p){            //一个chunk销毁,并且把内存交还给系统            --_S_total_chunk_cnt;            ::free(p);        }    };    //内存块,用于记录已分配内存    struct _mem_piece{            enum{            _mem_piece_struct_size = sizeof(char) + sizeof(int)        };        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::delete_chunk(_S_chunk_list[idx]);                    _S_chunk_list[idx] = NULL;                    ++cnt;                }            }            ++idx;        }        return (free_cnt * (_chunk::_chunk_struct_size + _new_chunk_len));    }}
复制代码

 

原创粉丝点击