垃圾回收之寫屏障
資料閱讀
有兩張不錯的gif圖(原子wikipedia) https://zhuanlan.zhihu.com/p/74853110
最先看的文章,引用了上面的文章 https://www.jianshu.com/p/4c5a303af470
在討論 Go 的混合寫屏障 https://github.com/changkun/go-under-the-hood/issues/20
解釋了爲啥有 read barrier https://www.zhihu.com/question/42879518/answer/437304734
這些個文章看完,總覺得其中對golang的混合寫屏障沒說透,甚至感覺有錯誤說法。
三色標記的理解
基本的mark-sweep算法
基本算法有個缺點,需要STW(stop the world),有較大延時。
三色標記法(tricolor mark-sweep)
三色標記的過程中,節點顏色變化:白色》灰色》黑色。當沒有灰色了,標記結束,黑色存活,白色清除。
三色標記可以實現增量回收和併發回收,能降低延時(latency);當然也有缺點,會降低吞吐量(throughput)。
三色標記正確運行需要滿足下面2個條件中的一個:
- 強三色不變式:黑色對象不引用白色對象。
- 弱三色不變式:黑色對象引用的白色對象可以通過灰色對象搜索到。
當使用增量回收和併發回收時,回收過程中,用戶程序會新建對象修改對象引用,會破壞上面的條件。寫屏障就是爲了處理這個事情。
有兩套方案
// 當obj是黑色時,標記插入的*prt對象至少爲灰色。滿足強三色條件 // GC運行時,新new出來的對象,可以直接標記成黑色。 djjkstra_write_barrier(obj, ref, ptr){ shade(ptr) // shade的做法時如果是白色就標記成灰色,否則不變。後面的僞代碼都是這個意思。 ref = ptr } // 從obj上刪除ref時,標記*ref至少爲灰色。這樣可以滿足弱三色條件 // 悲觀認爲所有被刪的對象都可能被黑色對象引用了,效率很低的樣子。 // **注意:**GC運行時,新new出的對象必須直接標記成黑色 yuasa_write_barrier(obj, ref, ptr){ shade(ref) // 當obj時黑色時,可以不標記,因爲刪掉黑色到白色之類引用關係不破壞弱三色條件 ref = ptr }
一般來說dijk就挺好的,但像golang這種希望寫棧上的引用時不使用寫屏蔽,提高性能,所以得找別的方法。
- golang早期用的dijk的方法,得把棧本身重新標記成灰色,mark的最後階段STW,然後掃描下棧,完成標記。這樣說是最後結算的延時可以達到100ms。
- golang 1.8 用yuasa的方法,也同樣存在同樣的問題。這時可以當棧還不是黑色時,所有複製操作,額外把ptr標記成灰色,就當是從棧裏刪除下來的。
僞代碼
// golang 1.8的方式,說法是結合了dij和yuasa。效率(吞吐量)比yuasa還低。 // **注意:**GC運行時,新new出的對象必須直接標記成黑色 golang_hybird_write_barrier(obj, ref, ptr){ shade(ref) // 當棧本身還沒完成掃描時,假定ptr就是從棧上刪除取下來的對象。當棧掃描完,可以當成標記爲黑色,這時就不需要補充標記了。 if current stack is grey: shade(ptr) ref = ptr }
這樣就可以實現,棧裏面的寫操作不需要barrier處理,掃描最後也不需要STW重新掃描棧。
歡迎關注我們的微信公衆號,每天學習Go知識