**作者:張富春(ahfuzhang),轉載時請註明作者和引用鏈接,謝謝!** * [cnblogs博客](https://www.cnblogs.com/ahfuzhang/) * [zhihu](https://www.zhihu.com/people/ahfuzhang/posts) * [G ...
作者:張富春(ahfuzhang),轉載時請註明作者和引用鏈接,謝謝!
使用 benchmark 壓測過程中通常會出現這樣的信息:
go test -v -bench=. -benchmem
f1 10000 120860 ns/op 2433 B/op 28 allocs/op
f2 10000 120288 ns/op 2288 B/op 26 allocs/op
可以看見 f1 在每次運行都產生了 28 次記憶體分配。
gc 通常是 golang 最大的性能殺手,減少記憶體分配對性能提升非常明顯。
可以把程式區分為 hot path
和 非hot path
,hot path 即運行最頻繁,消耗時間最多的程式執行路徑。VictoriaMetrics 的作者 Valyala 建議在 Hot path 上做到 0 alloc.
然而,必須需要在函數間傳遞的對象指針,必然需要引起 alloc。減少記憶體分配的一個辦法是 sync.Pool,但是如果在 A 函數中使用 sync.Pool.Get, 而在 B 函數中使用 sync.Pool.Put,這樣的程式流程比較混亂,不容易維護。且,當存在大量的不同對象時,其 sync.Pool 的種類也很多;sync.Pool 還有全局鎖,會影響程式的併發性。
VictoriaMetrics 中大量的使用了這樣的技巧:
1. 定義自己的 Context 對象
type MyContext struct{
}
// 業務函數的第一個參數都是 MyContext
func BizFunc1(ctx *MyContext){}
func BizFunc2(ctx *MyContext){}
2. 所有在函數間傳遞的變數(引起棧逃逸的),都定義在 MyContext 中
type MyContext struct{
tempBuffer []byte
}
// 如果函數都依賴 tempBuffer, 把局部變數定義到 MyContext 中
func BizFunc1(ctx *MyContext){
ctx.tempBuffer = append(ctx.tempBuffer, "str1"...)
}
func BizFunc2(ctx *MyContext){
ctx.tempBuffer = append(ctx.tempBuffer, "str2"...)
}
3. MyContext 本身從 sync.Pool 中獲取
var poolOfMyContext = sync.Pool{
New: func() interface{}{
return &MyContext{}
}
}
// 業務入口函數
func BizEntrance(){
ctx := poolOfMyContext.Get().(*MyContext)
defer poolOfMyContext.Put(ctx)
//
callBizFunc(ctx) // 業務邏輯函數
}
4. MyContext 對象提供 Reset() 方法
對分配好的各種緩衝區重用,避免反覆分配。
func (c *MyContext) Reset() {
c.tempBuffer = c.tempBuffer[:0] // 重用分配好的空間
}
// 業務入口函數
func BizEntrance(){
ctx := poolOfMyContext.Get().(*MyContext)
ctx.Reset() // 需要清空內容,避免上次的數據干擾運行結果
defer poolOfMyContext.Put(ctx)
//
callBizFunc(ctx) // 業務邏輯函數
}