1. 程式人生 > >【原創】技術系列之 記憶體管理(三)

【原創】技術系列之 記憶體管理(三)

作者:CppExplore 地址:http://www.cppblog.com/CppExplore/
(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);
   
return0;
}

    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);
   
return0;
}

    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要設計成這樣?能呼叫建構函式和解構函式顯然不是boost::object_pool類設計的出發點,因為建構函式只能執行預設建構函式(首次發表錯誤:可以呼叫任意的建構函式,參見程式碼檔案:boost/pool/detail/pool_construct.inc和boost/pool/detail/pool_construct_simple.inc,感謝eXile指正),近似於無,它的重點是記憶體釋放時候的清理工作,這個工作預設的解構函式就足夠了。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);
   
return0;
}

    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(); 
  
return0;
}

    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推薦使用後者。


未完 待續.................... 不過這個主題暫時不寫了 等有時間了