副標其實是原文的標題, 一語雙關蠻有意思的, “Getting to Go”意思是 “出發吧”, 但因為裏面的 “Go” 又同時就是 Go 語言的名稱. 這是個關於 Go 語言中以 Garbage Collection 角度討論 Go 歷程的官方部落格文章.
這篇文章紀錄了作者自 2014 至今對 Go 內部 GC 的改進與一路至今的歷程. 通篇的主軸在於從 Garbage Collection 的角度來分析看待 Go 語言. 相當值得一讀!
首先作者希望大家知道的是 Go 程式通常使用著 100,000+ 以上的 stack.
這些 stack 是被 Go scheduler 所管理著, 並且總是會被 GC 在安全點時搶佔. Go scheduler 將 Go routines 混合運行在期望能對應到每個 HW thread 的 OS threads 之上.
第二件重要的事情是 Go 是個 value-oriented 的程式語言, 這點與 C 類似而與一般其他需要執行環境 (runtime) 的 reference-oriented 程式語言不同. 如此的設計也帶來外部函式介面(foreign function interface, FFI) 的好處. Go 有著快速的 FFI. 由於不可能將全系統重新以 Go 語言打造, 因此 Go 必須透過 FFI 來與存取外部系統. FFI 這一點帶來許多驚人的事情, 這同時也是讓 Go 與其他同樣有著 GC 功能的語言有著重大差異的一點.
語言特性上需要注意, Go 允許 interior pointer (指向結構內特定欄位的 pointer), 此外 Go 本身為 ahead-of-time 編譯的語言, binary 即包含環境, 並不需要 runtime 以及 JIT.
大致上了解這些後, 作者帶大家回顧 Go 在 2014 在當時的挑戰:
在 2014 年 Go 飽受 GC latency 之苦. 可以這麼說, 若在 2014 年那時 Go 無法解決 GC 帶來的 latency 問題, Go 無法取得廣泛的成功. 而在當時許多其他的語言(像是 Rust)也同樣面臨著 latency 的問題, 但為何 latency 如此地重要?
因為數字是無情的, latency 是會不斷累積的. 一個 99%等級個別 GC 的服務品質目標(Service Level Objective), 像是 99% GC latency 小於 10ms, 並無法延伸. 重要的是整個期間的 latency 或全日中使用 App 的過程體驗. 因此在應用的情況下, 99% 這樣的等級可能結果是僅有 37% 的使用過程滿足 10ms 內延遲. 而期望達到 10ms 內延遲體驗的應用, 需要的是 4個9 或者是 99.99% 等級的目標. 作者稱其為4個9的暴政.
為了根本解決問題, 目標變成將 Garbage Collector 大幅轉為 concurrent 的形式, 而其中必須將用以處理 GC 處理的三色演算(tri-color algorithm), 必須轉為可平行化方式. 早期作者已在期刊提出並證明了 Dijkstra’s algorithm 能用以多執行緒運作. 對於 GC 常造成的系統停滯 (Stop The World, STW) 問題, 作者也證明了能有效處理.
在延遲性有方法處理後, 另一個棘手的問題在於 interior pointer. 而對此採取了尺寸隔離跨度 (size-segregated span) 的方式. GC必須有效地找出物件的起始位置, 若了解物件結構欄位的大小, 可以輕易的反推出物件的起始位置. 此外 size-segregated span 有著其他的優點:
- 低碎片化 (Low fragmentation)
- 內部結構 (Internal structures): 由於清楚知道物件分佈與結構, 如此可以得知低成本地分配方式.
- 速度 (Speed)
另外是在配置時針對每個 Word 配置 2-bit Mask 一方面注記用途上是 scalar 或者是 pointer, 另外也提供了除錯方面的用途.
最後是 GC Pacer, 其目的在於負責標記(marking)與配置(allocation), 並藉由每一次的 GC 過程學習去決定何時啟動下一次的 GC 機制.
文章在後半以 Twitter Server 的實例的統計圖表來說明 Go v1.4 到 v1.9 中 latency 變化, 圖表展現了這些改進在實際應用上的增進幅度. 自 300+ ms 到 ~0.5ms, latency 大幅度改善了600X 非常驚人!
後段還有談到 Failure 以及記憶體方面的趨勢, 而詳細就留給大家細細閱讀了.
沒有留言:
張貼留言