[原創]網絡專用高效內存池,支持多線程.原創,非sgi的內存池


  首先要說明一點,這個內存池使用時需要注意的,如果想用在長期分配出去的內存,請慎用.
  因為假如一個區塊被分配完了,只有在這個區塊里已分配的內存被完全釋放后,這個區塊才能重用.

因為當初是設計為網絡分包用的內存池.為了效率而采用這個策略的.

  發代碼之前先簡單介紹下內存池的思路.
  內存池分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 _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 = 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));
    }

}

 

 

  

  
  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM