一、堆内存与栈内存:
Golang程序中会有两个地方为变量分配内存空间,一个是全局的堆(heap),另外一个就是每个goroutine的栈(stack)空间。具体程序中的变量到底就分配到那里的内存是不需要开发者关注的,但是对于这两种内存空间分配性能差异是很大的。
二、指针逃逸
2.1. 什么是逃逸分析?
对于Golang程序,编译器是怎么判断一个变量到底是分配堆内存还是栈内存的呢?编译器决定内存分配位置的方式,就称之为逃逸分析(escape analysis)。逃逸分析由编译器完成,作用于编译阶段。
2.2. 指针逃逸
如果一个函数内部创建里一个对象(局部变量),但是在函数返回时是返回里该对象的指针,那么该变量的生命周期就变了,即使当前函数执行结束了,但是变量的指针还在,并不是随着函数结束就被回收的,那么这个局部连量就会被分配在堆上,这就产生了指针逃逸。
2.3. interface{} 动态类型逃逸
在golang中空接口interface{}可以是任意类型,因此编译期并不能确定其类型,所以也会被分配到堆上。
2.4. 栈空间不足
如果程序中需要分配一个空间比较大的局部变量,栈空间已经不够分配了,那么也会被分配到堆上。
2.5. 闭包
上方代码块中,Add() 返回的是一个闭包,并且该闭包访问了外部变量num,那么num将会被分配到堆上,因为num此时生命周期已经不会随着Add() 函数的结束而被回收,直到 fn 被销毁,num才会被回收。
三、回到最初的提问
3.1. 传值好还是传指着好:
传值会拷贝整个对象,而传指针只会拷贝指针地址,指向的对象是同一个。传指针可以减少值的拷贝,但是会导致内存分配逃逸到堆中,增加垃圾回收(GC)的负担。在对象频繁创建和删除的场景下,传递指针导致的 GC 开销可能会严重影响性能。
一般情况下,对于需要修改原对象值,或占用内存比较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值能够获得更好的性能。