go unescape

Golang escape analysis文章中有这样一段描述:

Stack-allocated variables, unlike heap-allocated variables, don’t incur any GC overhead because they’re destroyed when the rest of the stack frame is destroyed - when the function returns.

意思是说:在栈上申请的变量空间,在函数调用结束后会直接销毁,和堆不同。而在堆上申请的内存,需要通过gc来管理,会引入STW开销。

Go Escape Analysis Flaws介绍了一些go compiler不能识别的escapes,可以在写代码时引以借鉴。

思路大体是这样:go compiler需要识别出函数中可以在栈上分配的变量,函数内部生命周期结束的局部变量不考虑,所以compiler主要考虑input和output。可以以output为出发点推敲这个问题,如果output包含当前函数中变量的引用,则escape;或者input传入的变量,赋值给output,compiler不能识别,也会通过heap申请空间。

go scheduler中在进行栈操作时也涉及到了escape,如下:

_g_ := getg()
...
size := _g_.stack.hi
if size == 0 {
    size = 8192 * sys.StackGuardMultiplier
}
_g_.stack.hi = uintptr(noescape(unsafe.Pointer(&size)))

_g_是指向共享G空间的指针,修改它会在当前函数外起作用,size是局部变量,如果将size的引用赋值给_g_中的属性,会造成escape,那么size需要在堆上申请空间,而这里使用noescape逃过go compiler的检查。

此处添加另一个对于noescape的说明,golang compile//go:noescape放在没有方法体的方法上面,不允许通过参数传递进来的指针变量用到heap或者被写入到返回值中。

上面代码中为什么要对size屏蔽掉escape检查,防止size分配在heap上?如果去掉noescape调用,上面的操作针会被监察处escape?

我自己写了测试代码,理解在哪里可能还是有错误,并没有出现escape,只能带着这个问题继续下去。。。

package main

type S struct {
	a uintptr
}

func main() {
	ref(&S{a: 10})
}

func ref(s *S) *S {
	t := s.a

	var n uintptr = 100
	t = n

	s.a = t

	return s
}
go unescape
Share this