go——内存逃逸分析

1.首先解释一下什么是go中的内存逃逸分析:

go在编译阶段确定内存分配到栈上还堆上。
在go中这个操作是编译器来完成的,我们只需要通过go build -gcflags=-m通过行号即可清楚的观察到内存逃逸现象。

2.为什么要进行逃逸分析?

我们知道栈内存分配和释放都非常快,而堆内存需要go的垃圾回收机制来回收处理。如果变量都分配到堆空间中,它需要go频繁的调用垃圾回收机制来进行处理,而gc会占用大量的系统开销,所以通过逃逸分析来把不需要分配到堆内存的变量直接分配到栈上,以减少分配堆内存的开销和不必要的gc调用,提高程序运行效率。

3.简单介绍几种内存逃逸的案例

(1)函数内局部变量的逃逸:

func test() {
	a := []int{1,2,3}
	a[1]=4
}
func main() {
	test()
}

上边这一段代码是不会引起内存逃逸的,因为a是函数的局部变量,test函数无返回值,在main函数中调用完test后对a也无其他的操作,这里的a会被直接分配到栈内存中。而下边这段代码就有意思了哦

func test() []int{
	a := []int{1,2,3}
	a[1]=4
    return a
}
func main(){
  test()
}

注意一下与上边的不同,在test()函数中内部变量a作为函数返回值返回了。局部变量本来应该分配到栈上,在栈中回收。由于返回值要被外部变量引用,需要分配到堆上,但是在main中并没有使用这个返回值,造成了堆空间的浪费,这样的内存分配其实就不合理了。所以在这里其实是要进行优化。
(2)参数为interface接口类型。interface类型的变量由动态类型和值构成。参数为interface类型的在编译期间很难确定其参数的具体类型,也有可能产生逃逸。比如fmt.println其参数就是interface,通过go build -gcflags=-m可以发现在打印这一行发生了内存逃逸。
(3)自定义结构体类型我们需要注意在写初始化函数的时候是返回值类型还是指针类型。可能我们都默认返回指针类型,那是因为在实际环境中,我们一般都会将这个返回值作为参数传入其他函数进行进一步的调用处理,而像下边的代码中如果返回指针类型,但是在main函数中对其只有一步简单的操作,比如说打印或者对值进行修改而没有作为参数传入其他函数进行进一步的调用那么就会发生内存逃逸,所以在这个时候只要返回值类型就好。

type Stu struct {
	id int
}
func NewStu() *Stu{
	return &Stu{id:1}
}
func main(){
	stu := NewStu()
	fmt.Println(stu)
}

通过运行go build -gcflags=-m会发现&Stu literal escapes to heap也就是new函数中的返回值那一行发生了内存逃逸。所以我们在写代码的时候可以尽量规避这些行为。

猜你喜欢

转载自blog.csdn.net/weixin_43867526/article/details/107127030