首先要說明一點,這個內存池使用時需要注意的,如果想用在長期分配出去的內存,請慎用.
因為假如一個區塊被分配完了,只有在這個區塊里已分配的內存被完全釋放后,這個區塊才能重用.
因為當初是設計為網絡分包用的內存池.為了效率而采用這個策略的.
發代碼之前先簡單介紹下內存池的思路.
內存池分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)); } }