作者:张富春(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)  // 业务逻辑函数
}
内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/ahfuzhang/p/17566000.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!