本文希望分享一個輕量版增量垃圾回收器實現:TGC

目標:

  1. 不能stop the world
  2. 選擇使用,無需全局替換
  3. 使用方便,無需繁瑣的手動註冊
  4. 盡量輕量實現,方便集成和維護
  5. 盡量貼合C++自身特點實現

目標用戶

如Blink GC的需求一樣,用shared_ptr已經無法方便解決生命週期問題的情況。但是Blink GC太複雜(不然也不會有本文), Boehm GC有需要操作系統特殊支持等使用限制。

實現思路分析:

  1. 使用傳統的標記清除演算法的話,要不停頓,可以增量或者並行。
  2. C++不好處理內存整理的操作,因為C++內存內的數據直接拷貝會有很多限制。
  3. 並行回收會較大增加實現複雜度。
  4. python的引用計數加掃描標記可以很快回收臨時垃圾,C++有RAII
  5. 可用3色標記增量演算法,實現簡單,但吞吐量小
  6. 智能指方式針兼容性較好,可以和三方庫、遺留代碼並存
  7. 可以與已有內存池對接更好
  8. 不優先考慮:
    1. 性能,性能敏感和GC是矛盾的,要處理好太複雜
    2. 吞吐量,C++不像Java等語言一樣必須重度依賴GC

其實很多相關技術都很成熟了,所以這裡我無需重複,只想分享一下C++特定的問題:

最開始我是實現的引用計數加標記回收的方式,好處是大部分垃圾都可以用引用計數處理,循環引用才由掃描標記回收,但是實現中發現邏輯很複雜很不穩定。這有悖於輕量的原則,所以放棄了這種方式,全部由標記回收方式統一處理。

首先,GC中保存所有的指針用於掃描,也保存了每個對象對應的meta信息用於保存:回收狀態、類信息。類信息包括:成員指針偏移,構造析構器,子指針枚舉器。因為C++本身的限制,這些信息在編譯時間很難方便的獲取到(也不是不可以,但是大大增加了複雜度),所以TGC綜合權衡後,採用犧牲部分性能的動態方式。

怎麼發現根

  1. 默認構造的指針都是根,比如全局變數
  2. 指針註冊到GC中時查找owner對象(即包容這個指針成員的class實例)找不到即為根

怎麼跟蹤引用關係:

  1. Class對象中成員指針,指針構造時查找owner並註冊到owner的meta中。
  2. 容器中保存指針元素,對容器,特化子指針枚舉器,然後在標記階段通過枚舉器標記所有子指針為葉子。
  3. 所有可能保存指針的複合對象都需要為可跟蹤的,除了標準容器,還有function對象
  4. 函數棧上的指針怎麼處理?無需處理由於收集函數是手動在事件循環外觸發,此時已經退出大部分函數,RAII已經清除了棧上的指針。

容器這裡有個難點,在構造指針的時候無法很簡單的判斷出自己是否在容器中,因此推遲到枚舉器裡面標記,因為在枚舉器中一定就是葉子。

這裡很多細節沒有具體討論,可以查看TGC的實現。

限制:

  1. 如shared ptr一樣必須用指定方式構造對象
  2. 可以不使用gc容器,因為RAII可以保證指針銷毀時從gc自動清除。但是脫離gc容器可能引起循環引用無法釋放導致RAII無法執行析構,從而無法從gc中清除引用造成泄露。
  3. 從gc指針取出的原始指針,將脫離gc的管理。
  4. 因為三色標記的原因,掃描階段的指針變化需要特殊處理(write barrier),所以gc指針的複製操作有一點點消耗(tgc中的onPtrChanged實現)
  5. 為了簡化實現,未考慮異常和多線程。

由於只有小規模使用,因此TGC未必達到生產質量,請有興趣的朋友提issue。筆者能力有限,如有錯誤請不吝賜教。


推薦閱讀:
相關文章