sync.Pool不如上面的方法節省內存,但好處是可以縮容 。
數量可控的中型對象有的時候,我們可能需要一些定額數量的對象,并且對這些對象復用 。
這時可以使用channel來做內存池 。需要時從channel取出,用完放回channel 。
slice的復用fasthttp, VictoriaMetrics等組件的作者 valyala可謂是把slice復用這個技巧玩上了天,具體可以看fasthttp主頁上的Tricks with []byte buffers這部分介紹 。
概要的總結起來就是:[]byte這樣的數組分配后,不要釋放,然后下次使用前,用slice=slice[:0]來清空,繼續使用其上次分配好的cap指向的空間 。
這篇中文的總結也非常不錯:《fasthttp對性能的優化壓榨》
valyala大神還寫了個 bytebufferpool,對[]byte重用的場景進行了封裝 。
避免容器空間動態增長對于slice和map而言,在預先可以預估其空間占用的情況下,通過指定大小來減少容器操作期間引起的空間動態增長 。特別是map,不但要拷貝數據,還要做rehash操作 。
func xxx(){ slice := make([]byte, 0, 1024) // 有的時候,golangci-lint會提示未指定空間的情況 m := make(map[int64]struct{}, 1000)}大神技巧:用slice代替map此技巧源于valyala大神 。
假設有一個很小的map需要插入和查詢,那么把所有key-value順序追加到一個slice中,然后遍歷查找——其性能損耗可能比分配map帶來的GC消耗還要小 。
- map變成slice,少了很多動態調整的空間
- 如果整個slice能夠塞進CPU cache line,則其遍歷可能比從內存load更加快速
避免棧逃逸golang中非常酷的一個語法特點就是沒有堆和棧的區別 。編譯器會自動識別哪些對象該放在堆上,哪些對象該放在棧上 。
func xxx() *ABigStruct{ a := new(ABigStruct) // 看起來是在堆上的對象 var b ABigStruct // 看起來是棧上的對象 // do something // not return a // a雖然是對象指針,但僅限于函數內使用,所以編譯器可能把a放在棧上 return &b // b超出了函數的作用域,編譯器會把b放在堆上 。}valyala大神的經驗:先找出程序的hot path,然后在hot path上做棧逃逸的分析 。盡量避免hot path上的堆內存分配,就能減輕GC壓力,提升性能 。
fasthttp首頁上的介紹:這篇文章介紹了偵測棧逃逸的方法:
Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http
驗證某個函數的變量是否發生逃逸的方法有兩個:
? testProj go run -gcflags "-m -l" internal/test1/main.go# command-line-argumentsinternal/test1/main.go:4:2: moved to heap: ainternal/test1/main.go:5:11: main make([]*int, 1) does not escape
- go run -gcflags "-m -l" (-m打印逃逸分析信息,-l禁止內聯編譯);例:
? testProj go tool compile -S internal/test1/main.go | grep newobject 0x0028 00040 (internal/test1/main.go:4) CALL runtime.newobject(SB)
- go tool compile -S main.go | grep runtime.newobject(匯編代碼中搜runtime.newobject指令,該指令用于生成堆對象),例:
經驗總結擴展閱讀
- 持續集成指南:GitLab 的 CI/CD 工具配置與使用
- 信用卡怎么查消費明細
- 消費貸款申請產生的費用高嗎
- oppo賬號的姓名怎么修改
- 租房可以換鎖芯嗎合法么 租房換鎖費用誰來承擔
- 特斯拉可以用家用電充電嗎
- 拆線多久可以用祛疤膏?
- 衛生間和廚房用什么瓷磚?
- 肉蓯蓉副作用是什么
- 計米器怎么設置參數
