Golang 逃逸分析

1. 栈和堆

Heap:内存的分配和释放都由编译器进行管理,分配和释放的速度非常快

Stack:不会自动清理,会引起频繁地GC操作,而垃圾回收操作会占用较大的系统开销

2. 逃逸分析

逃逸分析是对变量放在堆上还是栈上的分析,该分析在编译阶段完成。

如果一个变量超过了函数调用的生命周期,即该变量在函数外部存在引用,编译器会把这个变量分配到堆上,此时该变量发生了逃逸。

go run -gcflags '-m -l' main.go

-m    print optimization decisions 代码编译优化情况,包括逃逸情况和函数是否内联
-l    disable inlining

补充:

-gcflags: 给编译器传递参数,go tool compile --help查看可用参数

-ldflags: 给链接器传递参数,go tool link --help 查看可用参数

3. 逃逸场景

3.1 interface{} 赋值

type Animal struct {
	Name interface{}
}

func main() {
	a := new(Animal)
	a.Name = "cat"
}

编译结果:

$ go build -gcflags '-m -l' ./main.go
./main.go:8:10: new(Animal) does not escape
./main.go:9:9: "cat" escapes to heap

优化方案:将 interface{} 改为确定类型

type Animal struct {
	Name interface{}
}

func main() {
	a := new(Animal)
	a.Name = "cat"
}

再次编译:

$ go build -gcflags '-m -l' ./main.go
./main.go:8:10: new(Animal) does not escape

3.2 传递指针

type Animal struct {
	Name string
}

func NewAnimal(name string) *Animal {
	return &Animal{
		Name: name,
	}
}

func main() {
	NewAnimal("dog")
}

编译结果:

$ go build -gcflags '-m -l' ./main.go
./main.go:7:16: leaking param: name
./main.go:8:9: &Animal{...} escapes to heap

结论:指针做为参数的好处在于减少值拷贝,但变量逃逸的开销可能会更大

3.3 栈空间不足

func main() {
	arr := make([]int, 10000)

	for i := range arr {
		arr[i] = i
	}
}

编译结果:

$ go build -gcflags '-m -l' ./main.go
./main.go:4:13: make([]int, 10000) escapes to heap

4. 强制避免逃逸

// $GOROOT/src/runtime/stubs.go
func noescape(p unsafe.Pointer) unsafe.Pointer {
    x := uintptr(p)
    return unsafe.Pointer(x ^ 0) // 任何数值与0的异或都是原数
}

Go标准库和运行时实现中,大量使用函数noescape函数。其实现逻辑:将传入的指针转换为uintptr数值类型,然后再次转换为指针。转换过程切断了逃逸分析的数据流跟踪,导致传入的指针避免逃逸

type Animal struct {
	Name string
}

func NewAnimal(name string) unsafe.Pointer {
	a := &Animal{
		Name: name,
	}

	return noescape(unsafe.Pointer(a))
}

func main() {
	a := (*Animal)(NewAnimal("dog"))
	fmt.Println(a)
}

编译结果:

go build -gcflags '-m -l' ./main.go
./main.go:8:15: p does not escape
./main.go:17:16: name does not escape
./main.go:18:7: &Animal{...} does not escape
./main.go:27:13: ... argument does not escape

5. 总结

  • 逃逸分析是编译器在静态编译时完成的
  • 逃逸分析可以确定哪些变量分配在栈上,栈的性能更好

参考资料:https://mp.weixin.qq.com/s?__biz=MzAxMTA4Njc0OQ==&mid=2651449591&idx=3&sn=c5eba0fc5cd4168565830d0a3c5436fb&chksm=80bb3605b7ccbf1352965e5cf2e577ce59732426dff2b0b4e5fb4d48c52c2d88d009b39d8476&scene=21#wechat_redirect

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值