Go1.20 實驗:內存 Arenas VS 傳統(tǒng)內存管理
https://mp.weixin.qq.com/s/1-744datNkVoTpvqaUaUQA

注意
Go arenas 是一個實驗性功能。API 和實現完全不受支持,Go 團隊不保證兼容性,也不保證在任何未來版本中繼續(xù)存在。
請通過這個 github 討論區(qū)[1]來了解更多信息。
簡介
Go 1.20 引入了一個實驗性的內存管理概念 “arenas”,可以用來提高Go程序的性能。在本博客文章中,我們將探討:
什么是 arenas
它們是如何工作的
如何確定你的程序是否可以從使用 arenas 中受益
我們如何使用arenas優(yōu)化我們的一項服務
什么是內存 Arenas
Go語言是一種利用垃圾回收機制的編程語言,這意味著運行時會自動幫助程序員管理內存的分配和釋放。這消除了手動內存管理的需求,但也帶來了代價:
Go 運行時必須跟蹤 *每個* 分配的對象,導致性能開銷增加。
在某些情形下,例如 HTTP 服務器處理具有大量 protobuf blob(其中包含許多小對象)的請求時,Go 運行時可能會花費大量時間跟蹤每個分配,然后釋放它們。因此,這也導致了明顯的性能開銷。
Arenas 提供了一種解決這個問題的方法,通過減少與許多小分配相關的開銷。在這個 protobuf blob 示例中,可以在解析之前分配一大塊內存(Arenas),以便所有已解析的對象可以放置在 arenas 內并作為一個整體單元進行跟蹤。
一旦解析完成,整個 arenas 可以一次性釋放,進一步減少釋放許多小對象的開銷。

判斷可以從 arenas 中受益的代碼
任何分配大量小對象的代碼都有可能從 arenas 中受益。但是如何知道代碼分配的過多?根據我們的經驗,最好的方法是對程序進行分析。
使用 Pyroscope,我們可以獲得其中一個云服務[2]的分配配置文件(alloc_objects
)。

你可以看到內存分配(533.30 M
)的大部分來自代碼的一個區(qū)域 - 這是在底部調用函數InsertStackA
的紫色節(jié)點。鑒于它代表65%的分配,這是使用 arenas 的好候選者。但是,通過減少這些分配是否可以獲得足夠的性能收益?讓我們看看同一服務的CPU分析(cpu
):

幾件事情很突出:
程序在相同的
InsertStackA
函數中花費了很多CPU時間,因此顯然有潛在的重要性能改進潛力。如果搜索
runtime.mallocgc
(底部的多個粉色節(jié)點),你會發(fā)現該函數在各種不同的地方頻繁調用,它占用了我們總執(zhí)行時間的約14%。大約5%的CPU時間花費在
runtime.gcBgMarkWorker
(位于火焰圖右側的粉色節(jié)點)上。
因此,理論上,如果我們優(yōu)化了這個程序中的所有分配,我們可以減少14%+5%= 19%的CPU時間。這將轉化為我們所有客戶的19%成本節(jié)約和延遲改進。在實踐中,不太可能真正使這些數字降到零,但這仍然是應用程序執(zhí)行的重要工作,可能值得優(yōu)化。
我們做出的優(yōu)化
如果您對此感興趣,您可以在 Pyroscope 存儲庫中找到公共拉取請求[3]作為參考。
首先,我們創(chuàng)建了一個包裝組件[4],負責處理切片或結構的分配。如果啟用了 arenas ,此組件使用 arenas 分配切片,否則使用標準“make”函數。我們通過使用構建標記(
//go:build goexperiment.arenas
)實現此目的。這允許在構建時輕松地在 arenas 分配和標準分配之間切換然后,我們在解析器代碼周圍添加了初始化[5]和清理[6]調用 arenas
接下來,我們用我們的包裝組件中的make調用替換了常規(guī)的
make
調用[7]最后,我們在啟用了 arenas 的情況下構建了 pyroscope,并逐漸部署到了我們的?Pyroscope Cloud[8]?生產環(huán)境中。
我們 Arenas 實驗的結論

上面的火焰圖表示我們實施更改后的配置文件。您可以看到,許多runtime.mallocgc
調用現在已經消失,但被 arenas 特定的等效項(runtime.(*userArena).alloc
)替代,您也可以看到垃圾回收開銷減少了一半。僅從火焰圖上看準確的節(jié)省量很難看出,但是當我們查看結合了火焰圖和AWS指標的CPU使用情況的 Grafana 儀表板時,我們發(fā)現CPU使用率大約減少了8%。這直接轉化為該特定服務的云賬單上的8%費用節(jié)省,使其成為一項有價值的改進。

這可能看起來不多,但重要的是要注意,這是一項已經被優(yōu)化得相當多的服務。例如,我們使用的 Protobuf 解析器根本不會分配任何額外的內存,垃圾回收開銷(5%)也在我們服務的開銷范圍的低端。我們認為代碼庫的其他部分還有很多改進的空間,因此我們很高興繼續(xù)嘗試 arenas 。
權衡弊端
雖然 arenas 可以提供性能上的好處,但在使用它們之前有必要考慮利弊。使用 arenas 的主要缺點是,一旦使用 arenas,您現在必須手動管理內存,如果您不小心,這可能導致嚴重問題:
未能正確釋放內存可能導致內存泄漏
嘗試從已釋放的場館訪問對象可能導致程序崩潰
以下是我們的建議:
僅在關鍵代碼路徑中使用 arenas。不要在所有地方使用它們
在使用 arenas 前后對代碼進行分析,以確保您在 arenas 可以提供最大效益的地方添加 arenas
密切關注在 arenas 上創(chuàng)建的對象的生命周期。確保不要將它們泄漏到程序的其他組件,其中對象可能超出 arenas 的生命周期
使用
defer a.Free()
確保不會忘記釋放內存使用
arena.Clone()
將對象克隆回堆上,如果您在 arenas 被釋放后想要使用它們
Go arenas 目前的一個主要缺點是它是一項實驗性特性。API 和實現是完全不受支持的,Go 團隊不對其兼容性或將來是否會繼續(xù)存在進行任何保證。我們建議您將所有與 arenas 相關的代碼提取到單獨的包中,并使用 build tags 以確保如果您決定停止使用 arenas,它很容易從您的代碼庫中刪除。我們的代碼[9]可作為此方法的演示。
解決社區(qū)關注的問題
Go團隊已經收到了關于 arenas 的大量反饋,我們想要回應社區(qū)中我們所看到的一些擔憂。有關 arenas 最常見的問題是它們添加了一種隱式且不立即顯現問題的程序崩潰方式,使語言變得更加復雜。
大部分的批評是明確的但誤導性的。我們不預期 arenas 會變得普遍。我們認為 arenas 是一個強大的工具,但只適用于特定情況。在我們看來, arenas 應該包含在標準庫中,但它們的使用應該受到警惕,就像使用unsafe
,reflect
或cgo
一樣。
我們對 arenas 的經驗非常充分,我們能夠證明 arenas 可以顯著減少垃圾回收和內存分配的時間。本文描述的實驗關注的是一個單獨的、已經高度優(yōu)化的服務,我們仍然能夠通過使用 arenas 獲得8%的額外性能。我們認為許多用戶可以從在代碼庫中使用 arenas 中獲益更多。
此外,我們還發(fā)現,相比我們過去嘗試的其他優(yōu)化(如使用緩沖池或編寫自定義無分配 protobuf 解析器), arenas 的實現更容易。與其他類型的優(yōu)化相比,它們具有相同的缺點,但提供了更多的好處 - 因此,在我們看來, arenas 是一個凈贏。我們希望未來能看到 arenas 成為標準庫的一部分(并且是常用包如 protobuf 或 JSON 解析器的一部分)。
總結
Arenas 對于優(yōu)化 Go 程序是一個強大的工具,特別適用于處理大量 protobuf 或 JSON 塊的情況。它們有可能帶來顯著的性能改進,但是需要注意的是它們是一個實驗性的功能,不保證兼容性或在未來版本中的存在。
我們建議您對應用程序進行分析,并在代碼庫的有限部分嘗試使用arenas,并將您的結果報告給 Go 團隊[10]。
相關鏈接:
[1]https://github.com/golang/go/issues/51317#issuecomment-1385623024[2]https://pyroscope.io/pricing/[3]https://github.com/pyroscope-io/pyroscope/pull/1804[4]https://github.com/pyroscope-io/pyroscope/pull/1804/files#diff-70ab4bbe796a97ad1a47d7970504296eff36b5307527ae2806d2b50f94f83a45[5]https://github.com/pyroscope-io/pyroscope/pull/1804/files#diff-32bf8c53a15c8a5f7eb424b21c8502dc4905ec3caa28fac50f64277361ae746fR417[6]https://github.com/pyroscope-io/pyroscope/pull/1804/files#diff-34edf37e55842273380ee6cb31c9245f31ed25aa6d7898b0f2c25145f17d8ea0R170[7]https://github.com/pyroscope-io/pyroscope/pull/1804/files#diff-abe15b6d3634170650f86bb7283aa15265de2197cffa969deda2dd5b26fcecd9R89-R92[8]https://pyroscope.io/pricing[9]https://github.com/pyroscope-io/pyroscope/pull/1804/files#diff-70ab4bbe796a97ad1a47d7970504296eff36b5307527ae2806d2b50f94f83a45[10]https://github.com/golang/go/issues/51317
原文地址:
Go 1.20 Experiment: Memory Arenas vs Traditional Memory Management | Open Source Continuous Profiling Platform (pyroscope.io)
原文作者:
Dmitry Filimonov
本文永久鏈接:translator/w07_Go_1_20_Experiment_Memory_Arenas_vs_Traditional_Memory_Management.md at master · gocn/translator (github.com)
譯者:zxmfke
校對:cvley
往期推薦

在Go中實施簡潔架構

Go 1.20新變化!第一部分:語言特性

Go 1.20正式發(fā)布,又變得巨快無比了
想要了解Go更多內容,歡迎掃描下方??關注公眾號,回復關鍵詞 [實戰(zhàn)群]??,就有機會進群和我們進行交流
