摘要:由此,對于這樣的場景并不是很友好,那有沒有能夠精確控制內(nèi)存,讓其在的倍數(shù)時準確控制呢內(nèi)存內(nèi)存這就需要出場了。利用這個特性,就能保證在的一倍時才能被觸發(fā),這樣就能夠比較精準控制的觸發(fā)時機。
關(guān)于 Go GC 優(yōu)化的手段你知道的有哪些?比較常見的是通過調(diào)整 GC 的步調(diào),以調(diào)整 GC 的觸發(fā)頻率。
這兩種方式的原理和效果都是一樣的,GOGC 默認值是 100,也就是下次 GC 觸發(fā)的 heap 的大小是這次 GC 之后的 heap 的一倍。
我們都知道 GO 的 GC 是標記-清除方式,當(dāng) GC 會觸發(fā)時全量遍歷變量進行標記,當(dāng)標記結(jié)束后執(zhí)行清除,把標記為白色的對象執(zhí)行垃圾回收。值得注意的是,這里的回收僅僅是標記內(nèi)存可以返回給操作系統(tǒng),并不是立即回收,這就是你看到 Go 應(yīng)用 RSS 一直居高不下的原因。在整個垃圾回收過程中會暫停整個 Go 程序(STW),Go 垃圾回收的耗時還是主要取決于標記花費的時間的長短,清除過程是非常快的。
設(shè)置 GOGC 基本上我們比較常用的 Go GC 調(diào)優(yōu)的方式,大部分情況下其實我們并不需要調(diào)整 GOGC 就可以,一方面是不涉及內(nèi)存密集型的程序本身對內(nèi)存敏感程度太低,另外就是 GOGC 這種設(shè)置比率的方式不精確,我們很難精確的控制我們想要的觸發(fā)的垃圾回收的閾值。
GOGC 設(shè)置的非常小,會頻繁觸發(fā) GC 導(dǎo)致太多無效的 CPU 浪費,反應(yīng)到程序的表現(xiàn)就會特別明顯。舉個例子,對于 API 接口來說,導(dǎo)致的結(jié)果的就是接口周期性的耗時變化。這個時候你抓取 CPU profile 來看,大部分的耗時都集中在 GC 的相關(guān)處理上。
如上圖,這是一次 prometheus 的查詢操作,我們看到大部分的 CPU 都消耗在 GC 的操作上。這也是生產(chǎn)環(huán)境遇到的,由于 GOGC 設(shè)置的過小,導(dǎo)致過多的消耗都耗費在 GC 上。
對 API 接口耗時比較敏感的業(yè)務(wù),由于這種接口一般情況下內(nèi)存占用都比較低,因為 API 接口變量的生命周期都比較短,這個時候 GOGC 置默認值的時候,也可能也會遇到接口的周期性的耗時波動。這是為什么呢?
因為這種接口本身占用內(nèi)存比較低,每次 GC 之后本身占的內(nèi)存比較低,如果按照上次 GC 后的 heap 的一倍的 GC 步調(diào)來設(shè)置 GOGC 的話,這個閾值其實是很容易就能夠觸發(fā),于是就很容出現(xiàn)接口因為 GC 的觸發(fā)導(dǎo)致額外的消耗。
那如何調(diào)整呢?是不是把 GOGC 設(shè)置的越大越好呢?這樣確實能夠降低 GC 的觸發(fā)頻率,但是這個值需要設(shè)置特別大才有效果,GOGC 一般需要設(shè)置 2000 左右。這樣帶來的問題,GOGC 設(shè)置的過大,如果這些接口突然接受到一大波流量,由于長時間無法觸發(fā) GC 可能導(dǎo)致 OOM。
由此,GOGC 對于這樣的場景并不是很友好,那有沒有能夠精確控制內(nèi)存,讓其在 10G 的倍數(shù)時準確控制 GC 呢?
這就需要 Go ballast 出場了。什么是 Go ballast,其實很簡單就是初始化一個生命周期貫穿整個 Go 應(yīng)用生命周期的超大 slice。
func main() { ballast := make([]byte, 10*1024*1024*1024) // 10G // do something runtime.KeepAlive(ballast)}
上面的代碼就初始化了一個 ballast,利用 runtime.KeepAlive 來保證 ballast 不會被 GC 給回收掉。
利用這個特性,就能保證 GC 在 10G 的一倍時才能被觸發(fā),這樣就能夠比較精準控制 GO GC 的觸發(fā)時機。
這里你可能有一個疑問,這里初始化一個 10G 的數(shù)組,不就占用了 10 G 的物理內(nèi)存呢? 答案其實是不會的。
package mainimport ( "runtime" "math" "time")func main() { ballast := make([]byte, 10*1024*1024*1024) <-time.After(time.Duration(math.MaxInt64)) runtime.KeepAlive(ballast)}
$ ps -eo pmem,comm,pid,maj_flt,min_flt,rss,vsz --sort -rss | numfmt --header --to=iec --field 5 | numfmt --header --from-unit=1024 --to=iec --field 6 | column -t | egrep "[t]est|[P]I"%MEM COMMAND PID MAJFL MINFL RSS VSZ0.1 test 12859 0 1.6K 344M 11530184
這個結(jié)果是在 CentOS Linux release 7.9 驗證的,我們看到占用的 RSS 真實的物理內(nèi)存只有 344M,但是 VSZ 虛擬內(nèi)存確實有 10G 的占用。
延伸一點,當(dāng)懷疑我們的接口的耗時是由于 GC 的頻繁觸發(fā)引起的,我們需要怎么確定呢?首先你會想到周期性的抓取 pprof 的來分析,這種方案其實也可以,但是太麻煩了。其實可以根據(jù) GC 的觸發(fā)時間繪制這個曲線圖,GC 的觸發(fā)時間可以利用 runtime.Memstats 的 LastGC 來獲取。
這張圖相同的流量壓力下,ballast 的表現(xiàn)明顯偏好
本篇文章只是簡單的闡述了 Go ballast 的使用,不過 Go ballast 是官方比較認可的方案,具體可以參見 issue 23044。很多開源程序,如 tidb,cortex 都實現(xiàn)了 go ballast,如果你的程序飽受 GOGC 的問題影響或者周期性的耗時不穩(wěn)定,不妨嘗試下 go ballast。
當(dāng)然強烈推薦你看下twitch.tv 這篇文章,相信讓你會對 GOGC 以及 ballast 的運用理解的更加透徹。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/123652.html
摘要:但是強迫癥犯了,為了使得性能達到極致,再次進行了深度的優(yōu)化。把移動中心設(shè)置在物體的重力中心,最為舒適。你可以狠狠的點擊預(yù)覽地址到此,組件,無論從性能,還是手感上來說,都已經(jīng)相當(dāng)?shù)姆衔覀兊男枨罅恕? 倉庫地址:Dragact手感絲滑的拖拽布局組件 預(yù)覽地址:支持手機端噢~ 上回我們說到,Dragact組件已經(jīng)進行了一系列的性能優(yōu)化,然而面對大量數(shù)據(jù)的時候,依舊比較吃力,讓我們來看看,優(yōu)化...
摘要:但是強迫癥犯了,為了使得性能達到極致,再次進行了深度的優(yōu)化。把移動中心設(shè)置在物體的重力中心,最為舒適。你可以狠狠的點擊預(yù)覽地址到此,組件,無論從性能,還是手感上來說,都已經(jīng)相當(dāng)?shù)姆衔覀兊男枨罅恕? 倉庫地址:Dragact手感絲滑的拖拽布局組件 預(yù)覽地址:支持手機端噢~ 上回我們說到,Dragact組件已經(jīng)進行了一系列的性能優(yōu)化,然而面對大量數(shù)據(jù)的時候,依舊比較吃力,讓我們來看看,優(yōu)化...
閱讀 2595·2023-04-25 20:50
閱讀 3953·2023-04-25 18:45
閱讀 2226·2021-11-17 17:00
閱讀 3332·2021-10-08 10:05
閱讀 3082·2019-08-30 15:55
閱讀 3498·2019-08-30 15:44
閱讀 2362·2019-08-29 13:51
閱讀 1120·2019-08-29 12:47