interface{}类型 + fmt.Sprintf() 导致栈逃逸

发布时间 2023-10-13 17:35:29作者: ahfuzhang

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


对部分代码进行了栈逃逸检查:

go build -gcflags="-m -m" pkg/*.go 2>&1 | grep -v "pb.go"

类似的位置,发生了栈逃逸:

s := fmt.Sprintf("%d", 123)

原因如下:
1.fmt.Sprintf()的参数(除了第一个)都是 interface{} 类型;
2.任何一个值传入fmt.Sprintf()都需要先转成 interface{} 类型,整数类型也被转成了 interface{} 类型;
3.因为对象要在函数间传递,必然需要把这个 interface{} 类型存储到堆上。
4.做 profile 分析的时候,就会发现这个地方存在 newobject() 的调用。

还有我对日志处理的包装,也存在类似问题:

func Debugf(formater string, args ...interface{}) {
    // ....
}

假设现在的日志级别是 info, debugf 应该不会损耗性能才对。
但其实是所有参数都会转换成 interface{} ,然后发生栈逃逸。

因此,有这样一些经验:
1.在需要高性能的场合,一定一定要做栈逃逸分析,然后逐行对着源码去分析;
2.interface{}用着很爽,但是对性能而言是个坏东西;看到它就要反问——会不会导致栈逃逸?
3.fmt.Sprintffmt.Errorf是个坏东西,需要性能的场合,建议老老实实用 strings.Builder{}.