火影 发表于 2023-7-19 16:39:06

golang: 模仿 VictoriaMetrics 中的做法,通过把局部变量放在自定义 Contex

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!

[*]cnblogs博客
[*]zhihu
[*]Github
[*]公众号:一本正经的瞎扯
https://img2022.cnblogs.com/blog/1457949/202202/1457949-20220216153819145-1193738712.png
使用 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)// 业务逻辑函数
}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: golang: 模仿 VictoriaMetrics 中的做法,通过把局部变量放在自定义 Contex