值类型分配位置由逃逸分析决定,可能在堆上;大结构体传参优先用指针,小结构体传值更优;结构体字段应降序排列以减少内存填充;sync.Pool推荐存指针而非大值类型。

值类型不一定在栈上,逃逸分析说了算
很多人以为 int、struct{} 这类值类型“天然在栈上”,其实 Go 编译器会根据变量是否“逃逸”来决定分配位置——它可能被放到堆上,哪怕你没写 new 或 &。关键看它会不会在函数返回后还被外部访问。
- 常见逃逸场景:
return &v、闭包捕获局部变量、把局部变量存进全局map或slice - 验证方式:用
go build -gcflags="-m"看输出,例如... moved to heap就是逃逸了 - 误区:写
&Vertex{1, 2}不等于“一定堆分配”;如果这个指针只在函数内用、没传出,编译器仍可能优化到栈上
大结构体传参时,别盲目复制
值类型赋值 = 深拷贝。一个含 [1024]byte 的结构体,每次传参就拷贝 1KB —— 高频调用下 CPU 和内存带宽都吃紧。
- 判断阈值:字段超过 4–5 个,或含
[]byte、map、[1024]byte等大字段,优先用*T传参 - 小结构体(如
type Point struct{X,Y int})传值反而更快,因为避免了指针解引用和潜在的堆分配 - 切片、map、chan 本身是轻量描述符(含指针+长度+容量),直接传值即可,不用加
*
结构体字段顺序影响真实内存占用
Go 按字段声明顺序 + 对齐规则布局内存,顺序不当会导致大量填充字节。比如 bool 后紧跟 int64,中间可能补 7 字节对齐空洞。
- 优化原则:字段按大小**降序排列**(
int64→int32→int16→bool) - 验证方法:
unsafe.Sizeof(T{})查实际大小,unsafe.Offsetof(t.field)看字段偏移 - 注意:字符串、切片、接口等引用类型本身占 16 字节(64 位系统),对齐要求是 8,但内部数据在堆上,不计入结构体大小
sync.Pool 存什么,直接影响复用效率
sync.Pool 存的是 interface{},底层会拷贝值。如果存的是大结构体,每次 Get() 都触发一次完整复制,反而放大开销。
立即学习“go语言免费学习笔记(深入)”;
- 推荐存
*T(如*bytes.Buffer),Get()只复制 8 字节指针,对象本体复用 - 避免存纯值类型(如
MyBigStruct),除非它极小(int32、struct{a,b int8})且无指针字段 - Pool 中对象生命周期不可控,不能依赖构造/析构逻辑,也不保证每次
Get()都命中缓存
真正难的不是记住规则,而是理解“为什么这个变量逃逸了”——很多时候问题出在一行看似无害的 fmt.Printf("%p", &x) 或闭包里多引用了一个局部变量。逃逸分析不是黑盒,它是可观察、可调试的工具,而不是玄学。










