亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

C++內存管理變革(6):通用型垃圾回收器 - Scope

系統 4047 0

本文已經遷移到: http://cpp.winxgui.com/cn:a-general-gc-allocator-scopealloc


C++內存管理變革(6):通用型垃圾回收器 - ScopeAlloc


許式偉
2008-1-22

引言

在前文,我們引入了 GC Allocator(具備垃圾回收能力的Allocator) ,并提供了一個實作: AutoFreeAlloc (詳細內容參見《 C++內存管理變革(2):最袖珍的垃圾回收器 - AutoFreeAlloc 》)。

但是,如前所述,AutoFreeAlloc是有其特定的適用環境的(它對內存管理的環境進行了簡化,這種簡化環境是常見的。詳細參閱《 C++內存管理變革(3):另類內存管理 - AutoFreeAlloc典型應用 》)。那么,在AutoFreeAlloc不能適用的情形下,我們可以有什么選擇?

本文要討論的,正是這樣一個GC Allocator實作。它所抽象的內存管理的環境比之AutoFreeAlloc復雜許多,適用范圍也廣泛很多。這個GC Allocator我們稱之為 ScopeAlloc 。

思路

在AutoFreeAlloc假象的模型里,一個算法的所有步驟都統一使用同一個GC Allocator,最后的內存由該Allocator統一回收。這個模型很簡潔,很容易理解和掌握。

理解ScopeAlloc的關鍵,在于理解我們對AutoFreeAlloc的模型所作的修正。我們設想一個算法的第i步驟比較復雜,其內存開銷也 頗為可觀,希望為步驟i引入一個私有存儲(Private GC Allocator),以便哪些步驟i內部計算用的臨時內存在該步驟結束時釋放。示意圖如下:

圖1

由于引入私有存儲(Private GC Allocator),模型看起來就變得很復雜。上面這個圖也許讓你看暈了。不過沒有關系,我們把上圖中與步驟i相關的內容獨立出來看,得到下圖:

圖2

如圖2顯示,一個算法會有自己的私有存儲(Private GC Allocator),也會使用外部公有的存儲(Share GC Allocator)。之所以是這樣,是因為算法的結果集(Result DOM)不能在算法結束時銷毀,而應該返回出去。這我們大致可以用以下偽代碼表示:

        
          ResultDOM
        
        
          * 
        
        
          algorithm
        
        
          (
        
        
          InputArgs
        
        
        
        
          args
        
        
          , 
        
        
          ScopeAlloc
        
        
          & 
        
        
          shareAlloc
        
        
          )
        
        
          
{
ScopeAlloc privateAlloc ( shareAlloc ) ;
...
ResultDOM * result = STD_NEW ( shareAlloc , ResultDOM ) ;
ResultNode * node = STD_NEW ( shareAlloc , ResultNode ) ;
result -> addNode ( node ) ;
...
TempVariable * temp = STD_NEW ( privateAlloc , TempVariable ) ;
...
return result ;
}

在這段偽代碼中,ScopeAlloc是今天的主角。 STD_NEW StdExt庫 中用于生成對象實例的宏,STD_NEW(alloc, Type)其功用等價于《 C++內存管理變革(1): GC Allocator 》中的New<Type>(alloc)。只是New<Type>模板函數比較“C++”,比較正統,也比較偏于理論 1 。而STD_NEW則是實際工程中的使用方式。

挑戰

你可能說,要引入私有存儲(Private GC Allocator),為什么非要提供一個新的Allocator?為什么不能是AutoFreeAlloc?為什么不能像下面這樣:

        
          ResultDOM
        
        
          * 
        
        
          algorithm
        
        
          (
        
        
          InputArgs
        
        
        
        
          args
        
        
          , 
        
        
          AutoFreeAlloc
        
        
          & 
        
        
          shareAlloc
        
        
          )
        
        
          
{
AutoFreeAlloc privateAlloc ;
...
ResultDOM * result = STD_NEW ( shareAlloc , ResultDOM ) ;
ResultNode * node = STD_NEW ( shareAlloc , ResultNode ) ;
result -> addNode ( node ) ;
...
TempVariable * temp = STD_NEW ( privateAlloc , TempVariable ) ;
...
return result ;
}

答案是,性能問題。我們這里 對AutoFreeAlloc和ScopeAlloc這兩個GC Allocator的性能進行了對比 ,結論如下:

生成一個新的AutoFreeAlloc實例是一個比較費時的操作,其用戶應注意做好內存管理的規劃。而生成一個ScopeAlloc實例的開銷很小,你甚至可以哪怕為生成每一個對象都去生產一個ScopeAlloc都沒有關系(當然我們并不建議你這樣做)。

對于多數的算法而言,我們不能確定它所需要的私有存儲(Private GC Allocator)的內存空間是多大?;蛘哒f,通常它們也許并不大。而在僅僅申請少量內存的情形下,使用AutoFreeAlloc是不太經濟的做法。 而相對的,無論算法所需的內存多少,使用ScopeAlloc都可以獲得非常平穩的性能。

故此,我們的第二個結論是:

AutoFreeAlloc有較強的局限性,僅僅適用于有限的場合(局部的復雜算法);而ScopeAlloc是通用型的Allocator,基本在任何情況下,你都可通過使用ScopeAlloc來進行內存管理,以獲得良好的性能回報。

實現

看到這里,你的興趣相信來了,很想知道ScopeAlloc是長什么樣。其實,ScopeAlloc只是另一個“AutoFreeAlloc”。我們來看看它的定義:

        
          typedef
        
        
        
        
          AutoFreeAllocT
        
        
          <
        
        
          ProxyBlockPool
        
        
          > 
        
        
          ScopeAlloc
        
        
          ;
        
      

而我們的AutoFreeAlloc它的定義是:

        
          typedef
        
        
        
        
          AutoFreeAllocT
        
        
          <
        
        
          DefaultStaticAlloc
        
        
          > 
        
        
          AutoFreeAlloc
        
        
          ;
        
      

詳細的代碼,參考以下鏈接:

可以看出,ScopeAlloc和AutoFreeAlloc唯一的區別,在于AutoFreeAlloc向系統申請內存(調用的是 malloc/free),而ScopeAlloc向一個內存池(即BlockPool,調用的是BlockPool:: allocate/deallocate)。

BlockPool

BlockPool 就是通常我們所說的 內存池(Memory Pool) 。但是它比一般的內存池要簡單很多,因為它只是管理MemBlock,而不負責對MemBlock進行結點(Node) 2 的劃分(這個工作實際上由AutoFreeAllocT完成了)。

BlockPool的規格如下:

        
          class
        
        
        
        
          BlockPool
        
        
          
{
BlockPool ( int cbFreeLimit , int cbBlock ) ;
void * allocate ( size_t cb ) ; // 申請一個MemBlock
void deallocate ( void * p ) ; // 釋放一個MemBlock
void clear () ; // 清空所有申請的內存
} ;

關于該類的實現細節,我并不多解釋,大家可以參考 內存池(MemPool)技術詳解 。我解釋下構造函數的兩個參數:cbFreeLimit、cbBlock是什么。

cbBlock

這個量比較好解釋,是指單個MemBlock的字節數。

cbFreeLimit

大家都知道,內存池技術在釋放內存時,它并不是將內存真的釋放(還給系統),而是記錄到一個FreeList中,以加快內存申請的速度。但是這帶來 的一個問題是,內存池隨著時間的推移,其占有的內存會不斷 地增長,從而不斷地吃掉系統的內存。cbFreeLimit的引入是為了限制了FreeList中的內存總量,從而抑制這種情況的發生。在 BlockPool中的FreeList內存達到cbFreeLimit時,deallocate操作直接釋放MemBlock。代碼如下:

      
        class BlockPool
        
{
public:
void deallocate(void* p) // 提醒:m_nFreeLimit = cbFreeLimit / cbBlock + 1
{
if (m_nFree >= m_nFreeLimit) {
free(p);
}
else {
_Block* blk = (_Block*)p;
blk->next = m_freeList;
m_freeList = blk;
++m_nFree;
}
}
}

ProxyBlockPool

它只是BlockPool的代理。定義如下:

        
          typedef
        
        
        
        
          ProxyAlloc
        
        
          <
        
        
          BlockPool
        
        
          > 
        
        
          ProxyBlockPool
        
        
          ;
        
      

而Proxy是什么?簡單地不能再簡單:

        
          template
        
        
           <
        
        
          class
        
        
        
        
          AllocT
        
        
          >
          
class ProxyAlloc
{
private :
AllocT * m_alloc ;

public :
ProxyAlloc ( AllocT & alloc ) : m_alloc ( & alloc ) {}

public :
void * allocate ( size_t cb ) { return m_alloc -> allocate ( cb ) ; }
void deallocate ( void * p ) { m_alloc -> deallocate ( p ) ; }
void swap ( ProxyAlloc & o ) { std :: swap ( m_alloc , o . m_alloc ) ; }
} ;

ScopeAlloc

如上所述,ScopeAlloc只是一個typedef:

        
          typedef
        
        
        
        
          AutoFreeAllocT
        
        
          <
        
        
          ProxyBlockPool
        
        
          > 
        
        
          ScopeAlloc
        
        
          ;
        
      

而關于AutoFreeAlloc的細節,前面《 C++內存管理變革(2):最袖珍的垃圾回收器 - AutoFreeAlloc 》中我們已經做了詳細介紹。

ThreadModel

關于 線程模型(ThreadModel) ,從上面給出的代碼( ScopeAlloc.h )中你可以看到相關的代碼。但是詳細的解釋超出了本文的范疇,我們會另外一篇專門解釋GC Allocator與線程模型(ThreadModel)之間的關系 3

時間性能分析

關于性能問題,我們前面已經作了 AutoFreeAlloc和ScopeAlloc的性能對比 。這里簡單再做一下分析。

內存申請/釋放過程

這兩個過程ScopeAlloc與AutoFreeAlloc基本差不多。考慮到ScopeAlloc使用了MemPool技術,從統計意義上來講,如果系統存在頻繁的內存申請和釋放,則ScopeAlloc性能略好于AutoFreeAlloc。

構造過程

基本上都只是指針賦值,可忽略不計。

析構過程

由于ScopeAlloc析構時將內存歸還給內存池,而不是還給系統,ScopeAlloc的時間性能要好過AutoFreeAlloc許多。更確 切地講,兩者的時間復雜度都是O(N),其中N為MemBlock的個數(也就是Allocator所占的內存總量),但是由于釋放MemBlock操作 的單位時間不同(BlockPool::deallocate比free快許多),導致兩者的性能有異。

使用樣例

AutoFreeAlloc和ScopeAlloc的性能對比 中當然不是ScopeAlloc的典型用例。這里我們舉一個:

        
          class
        
        
        
        
          Obj
        
        
          
{
private :
int m_val ;
public :
Obj ( int arg = 0 ) {
m_val = arg ;
printf ( " construct Obj: %d \n " , m_val ) ;
}
~
Obj () {
printf ( " destruct Obj: %d \n " , m_val ) ;
}
} ;

void testScope ()
{
std :: BlockPool recycle ;
std :: ScopeAlloc alloc ( recycle ) ;
printf ( " \n ------------------- global: have 3 objs ---------------- \n " ) ;
{
Obj * a1 = STD_NEW ( alloc , Obj )( 0 ) ;
Obj * a2 = STD_NEW_ARRAY ( alloc , Obj , 2 ) ;
printf ( " ------------------- child 1: have 4 objs ---------------- \n " ) ;
{
std :: ScopeAlloc child1 ( alloc ) ;
Obj * o1 = STD_NEW ( child1 , Obj )( 1 ) ;
Obj * o2 = STD_NEW_ARRAY ( child1 , Obj , 3 ) ;
printf ( " ------------------- child 11: have 3 objs ---------------- \n " ) ;
{
std :: ScopeAlloc * child11 = STD_NEW ( child1 , std :: ScopeAlloc )( child1 ) ;
Obj * o11 = STD_NEW ( * child11 , Obj )( 11 ) ;
Obj * o12 = STD_NEW_ARRAY ( * child11 , Obj , 2 ) ;
}
printf ( " ------------------- leave child 11 ---------------- \n " ) ;
printf ( " ------------------- child 12: have 3 objs ---------------- \n " ) ;
{
std :: ScopeAlloc child12 ( child1 ) ;
Obj * o11 = STD_NEW ( child12 , Obj )( 12 ) ;
Obj * o12 = STD_NEW_ARRAY ( child12 , Obj , 2 ) ;
}
printf ( " ------------------- leave child 12 ---------------- \n " ) ;
}
printf ( " ------------------- leave child 1 ---------------- \n " ) ;
printf ( " ------------------- child 2: have 4 objs ---------------- \n " ) ;
{
std :: ScopeAlloc child2 ( alloc ) ;
Obj * o1 = STD_NEW ( child2 , Obj )( 2 ) ;
Obj * o2 = STD_NEW_ARRAY ( child2 , Obj , 3 ) ;
}
printf ( " ------------------- leave child 2 ---------------- \n " ) ;
}
}

這個樣例中,child11是特別要注意的。請注意child11它是new出來的,我們忘記釋放它 4 。但是不要緊,在child1析構時,child11將會被刪除。

我們看到,有了ScopeAlloc,內存管理就可以層層規劃,成為一個內存管理樹(邏輯ScopeAlloc樹 5 )。你可以忘記釋放內存(事實上你不能釋放,只能clear),ScopeAlloc會記得為你做這樣的瑣事。這正是GC Allocator的精髓。

ScopeAlloc的名字來由,看這個樣例就可以體會一二了。在《 C++內存管理變革(1): GC Allocator 》我們特別提到,內存管理有很強的區域性。在不同的區域(Scope),由于算法不同,而導致對Allocator需求亦不同。從總體上來講,ScopeAlloc有更好的適應性,適合更為廣泛的問題域。

C++內存管理變革(6):通用型垃圾回收器 - ScopeAlloc


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 夜夜躁日日躁狠狠 | 91探花视频在线观看 | 国产三级久久久精品三级 | 亚洲国产精品热久久 | 欧美日韩亚洲综合久久久 | 日韩毛片在线免费观看 | 天天操天天干天天做 | 五月花激情网 | 国产在线成人a | 狠狠夜夜| 日韩精品你懂的在线播放 | 曰本毛片va看到爽不卡 | 手机看片日韩 | 亚洲精品二区 | 国产免费看 | 亚洲成a人片77777kkk | 国产性大片黄在线观看在线放 | 国产图片综合 | 五月激情婷婷综合 | 久久久久久青草大香综合精品 | 999在线视频 | 欧美日韩精品一区三区 | 久久精品免费看 | 国产亚洲午夜精品a一区二区 | 国产美女一级高清免费观看 | 91国内在线国内在线播放 | 在线观看中文字幕国产 | 日韩99| 中文字幕欧美在线观看 | 99在线热播精品免费 | 中文字幕久久久 | 中文精品久久久久国产网站 | 亚洲精品高清国产一线久久97 | 日本不卡在线播放 | 久久不卡精品 | 日韩专区中文字幕 | 国产伦精品一区二区三区高清 | 97久草| 九月婷婷亚洲综合在线 | 欧美成人精品欧美一级乱黄 | 成人毛片免费免费 |