RC Immix

RC Immix

Rifat Shariyar等,Reference Counting Immix,2013算法

目的

RC Immix算法將引用計數的一大缺點(吞吐量低)改善到了實用的級別。本算法改善了引用計數算法的「合併型引用計數法」和Immix組合起來使用。函數

合併型引用計數

Yossi Levanoni, Erez Petrank,2001指針

以前咱們說過,在吞吐量方面引用計數不如搜索型GC。其緣由是引用計數器頻繁增減。在引用計數中,每當對象之間的關係發生變化,對象的計數器就會發生變化。若是計數器頻繁發生增減,那麼寫入屏障執行的頻率就會增大,處理就會變得繁重。code

  • 在這裏注意下,若是咱們隊一個對象的計數器執行加後在執行減。由於二者會互相抵消,最終計數器並無變化。由此可知可知,比起一直保持計數器的數值正確,不如計數器的增量和減量相互抵消這樣更方便管理,節省資源。
  • 因而出現了一種新的方法,就是把注意力只放在初始和最後的狀態上,該期間內不對計數器進行修改。這就是合併型引用計數法(Coalesced Reference Counting)。在該方法中,即便指針發生改動,計數器也不會增減。指針改動時的信息會被註冊到更改緩衝區對象

  • 若是對象間的引用關係發生變化,就會致使計數器值是錯誤的。若是期間ZCT滿了就要去查找ZCT並更正計數器的值。
  • 在合併型引用計數法中,要將指針發生改動的對象和其全部子對象註冊到更改緩衝區中。這項操做是經過寫入屏障來執行的。不過由於這個時候咱們不更新計數器,因此計數器的值會保持錯誤。blog

  • 咱們將指針改動了的X和指針改動前被X引用的A註冊到緩衝區。由於沒有更新計數器,因此A和B的計數器在這個時候是不正確的。
  • 等到緩衝區滿了,就要運行GC了。合併型引用計數法中的GC指的是查找更改緩衝區。並正確設置計數器的過程。經過查找更改緩衝區,如何從新正確設定計數器的值,用下圖來講明。

  • 首先,X將其指針從A變動到B。此時咱們把X和其子對象A註冊到緩衝區。
  • 而後假設X的元引用對象發送了B->A->B這樣的變化。由於咱們已經吧X註冊到更改緩衝區了,因此沒有必要進行從新註冊。
  • 接下來,假設d階段更改緩衝區滿了,則是後就啓動GC了。首先查找更改緩衝區,咱們能夠獲得如下信息。
    • X在 某個階段引用的是A
    • X如今引用的是B
    • 對A的計數器進行減量
    • 對B的計數器進行加量。

僞代碼

# 合併型引用計數法的寫入屏障
write_barrier_coalesced_RC(obj, field, dst){
    if(!obj.dirty)
        register(obj)
    obj.field = dst
    
}

寫入屏障負責檢查要改動的指針的對象obj的標識dirty是否註冊完畢。若是沒有註冊,就將其註冊到更改緩衝區($mod_buf)。執行註冊的方法是register函數。內存

# register
register(obj){
    if($mod_buf.size <= $mod_buf.used_sized)
        garbage_collect()
        
    entry.obj = obj
    foreach(child_ptr :children(obj))
        if(*child_ptr != nil)
            push(entry.children, *child_ptr)

    push($mod_buf, entry)
    obj.dirty = true
    
}
  • 首先,當$mod_buf滿的時候,咱們就要執行GC。
  • 接下來,準備obj的信息。以將其註冊到更改緩衝區。這個entry是指向某對象全部子對象的指針集合。咱們將這個信息做爲$mod_buf的一個元素進行註冊。
  • 最後設置的dirty標識已經將其註冊過了。
#garbage_collect
garbage_collect(){
    foreach(entry: $mod_buf)
        obj = entry.obj
        foreach(child:obj)
            inc_ref_cnt(child)
        foreach(child : entry.children)
            dec_ref_cnt(child)
        obj.dirty = false
    clear($mod_buf)
    
}
  • 合併型引用計數法是將某一時期最初的狀態和最後的狀態進行比較,合理調整計數器的算法。
  • 在garbage_collect()中,先查找$mod_buf,對於已經註冊的對象進行以下處理。
    • 對obj如今的子對象的計數器進行增量
    • 對obj之前的子對象的計數器進行減量
      經過以上操做,根據對象最初的最後的狀態對計數器進行合理的調整。此外先進行增量是爲了確保AB是同一對象時也能後順利運行。

優勢和缺點

  • 優勢:增長了吞吐量
  • 缺點:增長了mutator的暫停時間。

合併型引用計數法和Immix的融合

RC Immix中,對象不只有計數器,線也有計數器,這樣就能夠獲悉線內是否存在活動對象。不過線的計數器和對象有所不一樣。對象的計數器表示的是指向這個對象的指針數。而線的計數器則是表示這個線裏存活的對象數量。若是變成了0就將整個線回收。資源

對象生成和廢棄的頻率要低於對象間引用關係變化的頻率,這樣一來 更新計數器所產生的額外負擔就小了。get


下面來講一下RC Immix中是如何以線爲單位進行內存管理的。

dec_ref_cnt(obj){
    obj.ref_cnt --
    if(obj.ref_cnt ==0)
        reclaim_obj(obj)
        line = get_line(obj)
        line.ref_cnt--
        if(line.ref_cnt == 0 )
            reclaim_line(line)
            
}

當對象的計數器爲0的時候,對線的計數器進行減量。當線的計數器爲0 的時候,咱們就能夠回收整條線。

此方法雖然把合併型引用計數和Immix組合到了一塊兒,可是不能執行壓縮。

在壓縮中要進行復制對象的操做。要實現這些操做,不只要複製對象,還要將引用此對象的指針所有改寫。所以壓縮所需的信息這裏是不能提供的。

因而RC Immix經過限定對象來時實現壓縮。就是下面要說的新對象。

新對象

在RC Immix中,把沒有經歷過GC的對象稱爲新對象。即就是在上一次GC以後生成的對象。

  • 更改緩衝區裏記錄的是從上一次GC開始到如今爲止指針改動過的對象。
  • 全部指向新對象的指針都是上一次GC以後生成的。也就是說,全部引用新對象的對象都被註冊到了更改緩衝區。
  • 所以能夠經過查找更改緩衝區,只對新對象進行復制操做。
  • 利用這條性質,RC Immix中以新對象爲對象進行壓縮,這種方法稱爲被動的碎片整理(Reactive Defragmentation)

被動的碎片整理

RC Immix,在更改緩衝區滿了的時候會查找更改緩衝區,這時若是發現了新的對象,就把他複製到別的空間去。

咱們準備一個空的塊來當作目標空間。在複製過程當中目標空間滿了的狀況下,就採用一個空的塊。咱們不對舊對象執行被動的碎片整理。RC Immix中的 garbage_collect()函數代碼清單以下。

garbage_collect(){
    dst_block = get_empty_block()
    foreach(entry :$mod_but)
        obj = entry.obj
        foreach(child_ptr :children(obj))
            inc_ref_cnt(*child_ptr)
            if(!(*child_ptr).old)
                reactive_defrag(child_ptr, dst_block)
        foreach(child : entry.children)
            dec_ref_cnt(child)
        obj.dirty = false
            
}

這裏的garbage_collect()函數和合並型引用計數法中的garbage_collect()函數很像。基本流程都是查找更改緩衝區,根據狀況增量或者減量操做。

不一樣的是這裏對新對象調用了reactive_defrag()函數,這就是被懂得碎片整理。那麼咱們來看看 reactive_defrag()函數。

reactive_defrag(ptr, dst_block){
    obj = *ptr
    if(obj.copied)
        *ptr = obj.forwarding
    else
        if(obj.size>dst_block.free_size)
            dst_block = get_empty_block()
        
        new_obj = dst_block.free_top
        copy_data(obj, new_obj, obj.size)
        obj.forwarding = new_obj
        *ptr = new_obj
        obj.copied = true
        new_obj.old = true
        dst_block.free_top += obj.size
        dst_block.free_size += obj.size
        line = get_line(obj)
        line.ref_cnt++
        
}
  • 複製對象並設定forwarding指針。在RC Immix中還須要留意線計數器。將對象複製到線時,也要對線的計數器進行增量。
  • 經過被動的碎片整理,就能夠以引用計數法爲基礎,來執行壓縮。
  • 此外,由於咱們以引用計數法爲基礎,因此不能解決循環引用的問題。
    爲了解決問題,可使用積極地碎片整理(Proactive Defragmentation)

積極的碎片整理

被動的碎片整理的兩處缺陷

  • 沒法對舊對象進行壓縮
  • 沒法回收有循環引用的垃圾

爲了解決這些問題,RC Immix中進行了被動的碎片處理以外,還進行了另外一項操做。也就是積極的碎片整理

  • 首先決定要複製到那個塊,而後把可以經過指針從根查找到的對象全都複製過去。
  • 經過積極的碎片整理,對就對象進行壓縮和回收循環垃圾都成爲了可能。
  • 他還有以個優勢就是能夠重置計數器。若是某個對象發生計數器溢出,經過執行積極的碎片整理,就會從根從新查找全部指針,也就能從新設定計數器的值。

優勢和缺點

優勢

  • 吞吐量獲得改善,聽說平均提升了12個點。甚至會超過搜索型GC。
  • 吞吐量改善的緣由是由於合併型引用計數法沒有經過寫入屏障來執行計數器的增減操做。即便對象之間的引用關係頻繁發生變化,吞吐量也不會降低太多。
  • 吞吐量改善的緣由是撤出了空閒鏈表。經過以線爲單位來管理分塊,只要在線內移動指針就能夠進行分配。此外省去了把分塊從新鏈接到空閒鏈表上的操做。

缺點

  • 會增長暫停時間,不過能夠經過調整緩衝區的大小縮短暫停時間。
  • 只要線內還有一個非垃圾對象,就沒法將其回收。也就是說線內只要有一個活動對象就會浪費一條線。
相關文章
相關標籤/搜索