Go GC 重大改進:Green Tea 性能提升 10-40%!
最近 Go 1.25 發(fā)布了一個讓人眼前一亮的新特性——Green Tea 垃圾回收器。
據(jù)說 Google 內(nèi)部已經(jīng)在生產(chǎn)環(huán)境大規(guī)模使用了,GC 時間直接降低了 10%左右,有些場景甚至能減少 40%。
今天我們就來好好聊聊這個 Green Tea 到底是什么東西,為什么能有這么大的性能提升。
背景
Go GC 階段介紹
Go 的垃圾回收器用的是標記-清除(mark-sweep)算法。這個算法本身不復(fù)雜,核心就是兩個階段:
1、標記階段:從根對象(全局變量、局部變量)開始,沿著指針一路追蹤,把能訪問到的對象都標記為"訪問過"。這就像圖的遍歷一樣,深度優(yōu)先或廣度優(yōu)先都行。
2、清除階段:把沒標記到的對象清理掉,釋放內(nèi)存。
Go 程序花在垃圾回收上的 CPU 時間其實挺多的。不少程序要花 20% 甚至更多的 CPU 時間在 GC 上,這個開銷確實有點大。
“問題” 出在標記階段。
標記階段的性能瓶頸
從多年的性能分析來看,Go 團隊發(fā)現(xiàn)了兩個關(guān)鍵問題:
1、GC 大約 90%的時間都花在標記上,只有 10%在清除。清除已經(jīng)優(yōu)化得很好了,但標記這塊還有很大空間。
2、標記時間里,至少 35%的時間是在等內(nèi)存訪問。這個問題更要命,因為它會讓現(xiàn)代 CPU 的各種優(yōu)化手段都失效。
為什么標記階段這么慢?
問題的根源在于內(nèi)存訪問模式。我們來看個例子:

這是一個簡單的堆內(nèi)存示意圖。左邊是根對象(全局變量 x 和 y),右邊是堆上的對象。每個矩形代表一個對象,對象之間用指針連接。
傳統(tǒng)的標記算法是怎么工作的呢?我們來跟著它走一遍:

從根對象開始,沿著指針一個個遍歷...

最后把所有可達對象都標記完成。
看起來挺正常的,但關(guān)鍵問題是訪問路徑:

注意到?jīng)]有?GC 在內(nèi)存里跳來跳去,做一點點工作就跳到另一個地方。這就像在城市街道開車,到處都是紅綠燈和轉(zhuǎn)角,根本跑不起來。
CPU 的痛苦
現(xiàn)代 CPU 有很多緩存優(yōu)化。訪問緩存里的數(shù)據(jù)可能只要 1-2 個時鐘周期,但訪問主內(nèi)存可能要 100 個周期!CPU 會自動把最近訪問過的數(shù)據(jù)和附近的數(shù)據(jù)加載到緩存。
可是傳統(tǒng)的標記算法完全不管這些。兩個有指針關(guān)系的對象,在內(nèi)存里可能隔得十萬八千里。CPU 的緩存就白費了,每次都要等內(nèi)存訪問。
更糟糕的是,這個問題還在變得越來越嚴重:
- NUMA 架構(gòu):現(xiàn)在內(nèi)存往往綁定在特定的 CPU 核心上,跨核心訪問更慢了
 - 內(nèi)存帶寬下降:CPU 核心越來越多,但每個核心能用的內(nèi)存帶寬反而下降了
 - 并行化瓶頸:雖然 Go 的 GC 是并行執(zhí)行的,但多核之間共享工作隊列會產(chǎn)生競爭
 
Green Tea 的核心思想
那怎么解決這個問題呢?Go 團隊提出了一個看似簡單,但極其巧妙的想法:
以頁(page)為單位工作,而不是以對象為單位
聽起來很簡單對吧?但要讓這個想法真正落地,需要解決很多技術(shù)細節(jié)。
具體來說:
- 不再掃描單個對象,而是掃描整個頁
 - 工作列表里存的不是對象,而是頁
 - 雖然最終還是要標記對象,但元數(shù)據(jù)是按頁組織的
 
什么是頁?
在 Go 的內(nèi)存管理中,頁(page)是一個 8KB 的連續(xù)內(nèi)存塊。
同一個頁里的對象大小都是一樣的。這是 Go 堆內(nèi)存的基本組織單位。

看這張圖,A、B、C、D 都是不同的頁。每個頁里包含若干個相同大小的對象槽位。
Green Tea 怎么工作?
我們還是用剛才那個例子,但這次用 Green Tea 來標記。

注意到了嗎?現(xiàn)在每個對象有兩個 bit 的元數(shù)據(jù):
- seen bit:表示是否見過這個對象的指針
 - scanned bit:表示是否掃描過這個對象
 
為什么需要兩個 bit?因為 Green Tea 的工作列表存的是頁,不是對象。我們需要額外記錄一個頁里哪些對象已經(jīng)掃描過了。

從根對象出發(fā),發(fā)現(xiàn)了一個對象,但這次我們把整個頁 A 加入工作列表,而不是單個對象。

同樣,第二個根對象指向的對象在頁 C,我們把頁 C 加入工作列表。

現(xiàn)在開始處理工作列表。取出頁 A,掃描它上面的對象,發(fā)現(xiàn)指向頁 B 的指針,就把頁 B 加入工作列表。
關(guān)鍵點來了!

當(dāng)我們處理頁 B 的時候,發(fā)現(xiàn)它上面累積了兩個對象需要掃描。我們可以連續(xù)掃描這兩個對象,它們在內(nèi)存里是相鄰的!
這就是 Green Tea 的魔法所在。通過以頁為單位工作,我們可以讓對象在頁上累積,然后一次性掃描多個相鄰的對象。

最后掃描完成,清除不可達的對象。
路徑對比
我們來對比一下兩種算法的訪問路徑:
傳統(tǒng)算法:
需要 7 次獨立的掃描操作,到處跳來跳去。
Green Tea:
只需要 4 次掃描,而且每次掃描的范圍更大,訪問的內(nèi)存更連續(xù)!
這就像終于從城市街道開到了高速公路上。CPU 可以充分利用緩存,減少主內(nèi)存訪問,整個流程順暢多了。
更進一步:向量化加速
Green Tea 還有個殺手锏——利用 AVX-512 向量指令加速。
傳統(tǒng)的圖遍歷算法沒法用向量指令,因為每次處理的對象大小都不一樣,沒有規(guī)律性。但 Green Tea 不一樣,同一個頁里的對象大小都是固定的,元數(shù)據(jù)的位置也是固定的。
現(xiàn)代 x86 CPU 支持 AVX-512 指令集,有 512 位寬的向量寄存器。一個頁的所有元數(shù)據(jù)可以放在兩個寄存器里,用幾條指令就能處理完一整個頁!
向量化掃描流程
簡單來說,這個流程是這樣的:
- 加載元數(shù)據(jù):把頁的 seen 和 scanned 位都加載到向量寄存器
 - 計算差異:用位運算找出哪些對象需要掃描
 - 展開位圖:把"每個對象一個 bit"展開成"每 8 字節(jié)一個 bit"
 - 找出指針:和頁的指針/標量位圖做交集,定位所有指針的位置
 - 批量處理:用向量指令一次處理 64 字節(jié)的數(shù)據(jù)
 
這里有個很酷的指令VGF2P8AFFINEQB,它可以高效地做位圖展開操作。
這是 Intel Ice Lake 和 AMD Zen 4 引入的新指令,專門用來做 bit 級別的矩陣變換。
有了向量化,掃描速度可以再提升 10% 左右。
性能表現(xiàn)
那么 Green Tea 到底有多快?
在 Go 團隊的基準測試中:GC 的 CPU 開銷普遍降低 10%-40%。
也就是如果一個程序本來花 10%的時間在 GC 上,那么 Green Tea 可以讓總體 CPU 使用率降低 1%-4%。
Google 內(nèi)部已經(jīng)大規(guī)模部署了 Green Tea,看到了類似的性能提升。
當(dāng)然也不是所有場景都能受益。Green Tea 的效果取決于能否在頁上累積足夠多的對象。
如果一個頁每次只能掃描一個對象,那可能反而會有輕微的性能損失。
不過好消息是,實踐中發(fā)現(xiàn)只要每次能掃描頁面 2% 的內(nèi)容,就能比傳統(tǒng)算法快。這個門檻其實挺低的。
如何使用
Go 1.25 已經(jīng)包含了 Green Tea,但默認不開啟。你可以通過環(huán)境變量啟用它:
GOEXPERIMENT=greenteagc go buildGo 團隊計劃在Go 1.26 把 Green Tea 設(shè)為默認,不過你還是可以選擇退出:
GOEXPERIMENT=nogreenteagc go buildGo 1.26 還會加入前面提到的向量化優(yōu)化,以及基于反饋做的各種改進。
背后的故事
Green Tea 看起來是個簡單的想法,但實際上是多年探索的結(jié)晶。
這個想法的種子早在 2018 年就埋下了。Go 團隊的 Michael Pratt、Cherry Mui、David Chase、Keith Randall 等人都貢獻了關(guān)鍵想法。Intel 的 Yves Vandriessche 提供了微架構(gòu)層面的洞察。

Green Tea 這個名字是 2024 年 Austin 在日本咖啡店巡游時想出來的,當(dāng)時他喝了很多抹茶,同時在做原型驗證。
到 2025 年,Michael 把 Green Tea 完整實現(xiàn)并投入生產(chǎn)。這期間想法還在不斷演化和改進。
總結(jié)
Green Tea 通過以頁為單位組織 GC 工作,顯著改善了內(nèi)存訪問模式,讓 CPU 可以更好地利用緩存,減少主內(nèi)存訪問。再加上向量化加速,性能提升相當(dāng)可觀。
這個改進對 Go 生態(tài)意義重大。畢竟 GC 性能一直是 Go 被詬病的點之一。現(xiàn)在有了 Green Tea,Go 程序的整體性能又能上一個臺階。
Go 1.25 已經(jīng)可以體驗了,Go 1.26 就會默認啟用。強烈建議大家試試看。















 
 
 











 
 
 
 