Golang實時GC的理論與實踐總結
Pusher是一個簡單的託管API,透過WebSockets整合到網路和移動應用程式或任何其他網際網路連線的裝置上,實現快速,輕鬆,安全地將實時雙向功能。每天,Pusher實時傳送數十億條訊息:從源傳送資訊到目標只需要100ms。我們如何實現這一目標? 一個關鍵因素是Go的低延遲垃圾回收器。
垃圾收集器是實時系統的緩慢的元兇,因為他們會暫停程式執行。所以在設計我們的新訊息匯流排時,我們仔細選擇了語言。 Go注重低延遲 ,但我們很謹慎:Go真正實現這一目標? 如果是,如何?
在這篇博文中,我們將看看Go的垃圾收集器。 我們將看看它是如何工作的(三色演算法),為什麼它這麼傑出的工作(實現這樣短的GC暫停),最重要的是,它是否真的這樣工作(對GC暫停進行基準測試,並與其他語言進行比較)。
從Haskell到Go
我們一直在構建的系統是一個將已釋出訊息儲存記憶體的pub / sub訊息匯流排。Go這個版本是我們在用Haskell完成後第一個實現的重寫。在發現GHC的垃圾收集器底層延遲問題後,我們在5月停止了對Haskell版本的工作。
GHC的暫停時間與工作集的大小(即記憶體中的物件數量)成正比。在我們的例子中,我們在記憶體中有很多物件,這導致了幾百毫秒的暫停時間。 這是一個問題,任何GC當它完成收集時會阻塞程式。
進入Go以後,它不像GHC的停止世界收集器,Go的收集器與程式同時執行,這使得避免更長的停頓成為可能。 我們被Go注重低延遲特性鼓勵,並發現它很有前途,特別是當我們讀到其每一個新版本都有關改善延遲時。
併發垃圾收集器如何工作?
Go的GC如何實現這種併發? 其核心是三色標記-清除(tricolor mark-and-sweep)演算法 。下面是一個動畫(見原文動畫),顯示了演算法的工作原理。 注意它讓GC與程式同時執行;
這意味著暫停時間成為排程問題。 排程程式可以配置為僅在短時間內執行GC集合,與程式交織。 這對我們的低延遲要求是個好訊息!
上面的動畫詳細顯示了標記階段。 GC仍然有兩個停止世界階段:對根物件的初始堆疊掃描,以及標記階段的終止。 令人興奮的是,這種終止階段,最近被淘汰 。 我們將在後面討論這個最佳化。 在實踐中,我們發現對非常大的堆這些階段的暫停時間為<1ms,。
使用併發GC,也有可能在多個處理器上並行執行GC。
延遲與吞吐量
如果併發GC針對很大的堆產生很低的延遲,為什麼要使用stop-the-world收集器?是不是Go的併發垃圾收集器比GHC的stop-the-world的收集器更好 ?
不一定。 低延遲是要付出成本的。 最重要的成本降低吞吐量 。併發性需要額外的工作來同步和複製,這使得程式可以做有用的工作。GHC的垃圾收集器針對吞吐量進行了最佳化,但Go針對了延遲最佳化。 在Pusher,我們關心延遲,所以對於我們這是一個很好的方案。
併發垃圾回收的第二個成本是不可預測的堆增長。程式可以在GC執行時分配任意數量的記憶體。這意味著GC必須在堆達到目標最大之前執行。 但是如果GC執行得太快,那麼將執行更多的收集。 這個代價是非常棘手。在Pusher,這種不可預測性不是一個問題;我們的程式傾向於以可預測的恆定速率分配儲存器。
它在實踐中如何執行?
到目前為止,Go的GC看起來很適合我們的延遲要求。 但它在實踐中如何執行?
今年早些時候,當調查Haskell實現中的暫停時間時,我們為測量暫停建立了一個基準測試。基準程式重複地將訊息推送到大小受限的緩衝區中。 舊訊息不斷地過期並變成垃圾。堆大小保持很大,這很重要,因為必須遍歷堆才能檢測哪些物件仍被引用。這就是為什麼GC執行時間與它們之間的活物件/指標的數量成比例。
這裡是Go中的基準,其中緩衝區被建模為陣列:
package main import ( "fmt" "time" ) const ( windowSize = 200000 msgCount = 1000000 ) type ( message []byte buffer [windowSize]message ) var worst time.Duration func mkMessage(n int) message { m := make(message, 1024) for i := range m { m[i] = byte(n) } return m } func pushMsg(b *buffer, highID int) { start := time.Now() m := mkMessage(highID) (*b)[highID%windowSize] = m elapsed := time.Since(start) if elapsed > worst { worst = elapsed } } func main() { var b buffer for i := 0; i < msgCount; i++ { pushMsg(&b, i) } fmt.Println("Worst push time: ", worst) } |
結合其它人關於Ocaml Racket 和Java的基準測試,下面是在我係統上的測試結果,右邊是最長暫停時間ms:
基準 最長暫停 (ms) OCaml 4.03.0 (map based) (manual timing) 2.21 Haskell/GHC 8.0.1 (map based) (rts timing) 67.00 Haskell/GHC 8.0.1 (array based) (rts timing) [1] 58.60 Racket 6.6 experimental incremental GC (map based) 144.21 (tuned) (rts timing) Racket 6.6 experimental incremental GC (map based) 124.14 (untuned) (rts timing) Racket 6.6 (map based) (tuned) (rts timing) [2] 113.52 Racket 6.6 (map based) (untuned) (rts timing) 136.76 Go 1.7.3 (array based) (manual timing) 7.01 Go 1.7.3 (map based) (manual timing) 37.67 Go HEAD (map based) (manual timing) 7.81 Java 1.8.0_102 (map based) (rts timing) 161.55 Java 1.8.0_102 G1 GC (map based) (rts timing) 153.89 <p class="indent"> |
很驚訝Java兩個測試表現很差,而OCaml表現非常好,不到〜3毫秒暫停時間,是由於為老一代使用增量的GC演算法。 (我們不選擇OCaml的主要原因是它支援多核並行性不好)。
如你所見,Go執行順利,暫停時間約為7ms。 這符合我們的要求。
..
結論
這項GC調查的關鍵是針對更低的延遲或更高的吞吐量進行最佳化。他們也可能執行更好或更差,這取決於您的程式堆使用率。 (有很多的物件嗎?他們有長或短的生命嗎?)
重要的是要了解底層的GC演算法,以決定它是否適合您的用例。 在實踐中測試GC實現也很重要。您的基準測試應該與您打算實現的程式具有相同的堆使用率。 這將在實踐中檢查GC實施的有效性。正如我們所看到的,Go的實現不是沒有故障,但在我們的情況下,問題是可以接受的。 我想在更多的語言中看到相同的基準,如果你想貢獻:)
儘管有一些問題,Go的GC相比其他GCed語言執行得很好。Go團隊一直在改進延遲,並繼續這樣做。 我們對Go的GC,理論和實踐感到滿意。
相關文章
- Node直出理論與實踐總結
- Web效能優化之 “直出” 理論與實踐總結Web優化
- 談理論與實踐
- https理論與實踐HTTP
- 【GoLang】golang 最佳實踐彙總Golang
- js正則理論與實踐JS
- 理解RESTful:理論與最佳實踐REST
- 馴服爛程式碼之實踐、總結與討論
- 三層架構--理論與實踐架構
- Java 理論與實踐: 關於異常的爭論Java
- 生產服務GC調優實踐基本流程總結GC
- Java 執行緒池的理論與實踐Java執行緒
- 對SVN的落地與實踐總結
- Java 理論與實踐: 併發集合類Java
- 深入解析Rivest Cipher 4:理論與實踐
- 密碼學與密碼安全:理論與實踐密碼學
- 理論+實踐解析“IT治理”之模式與原則模式
- 【軟體工程理論與實踐】Homework(四.1)軟體工程
- 理論指導實踐薦
- 【智慧製造】首鋼智造的理論探索與實踐
- 決策樹在機器學習的理論學習與實踐機器學習
- 討論:十年專案管理最佳實踐與經驗總結專案管理
- [Docker]寫 Dockerfile 的最佳實踐理論Docker
- 從Monolith到微服務:理論與實踐 - Kent BeckMono微服務
- 淺入淺出深度學習理論與實踐深度學習
- 圖書 "ERP理論 方法與實踐" 目錄
- Oracle10g Data Guard (Standby) 理論與實踐Oracle
- 嚴建兵 | 玉米基因組育種的理論與實踐
- js匯入匯出總結與實踐JS
- RESTful API實踐總結RESTAPI
- hadoop實踐總結Hadoop
- Linux實踐總結Linux
- UI設計培訓之如何將設計理論與實踐相結合UI
- LSTM的備胎,用卷積處理時間序列——TCN與因果卷積(理論+Python實踐)卷積Python
- 機器學習基礎篇:支援向量機(SVM)理論與實踐機器學習
- 【軟體工程理論與實踐】Homework(一.2,3)軟體工程
- Java理論與實踐:正確使用Volatile變數Java變數
- Java 理論與實踐: 正確使用 Volatile 變數Java變數