值传递在结构体≤16字节(如struct{x,y int}、time.time、string header)时通常更快,因避免解引用、利于缓存与内联;大结构体(≥256字节)或含不可拷贝字段、需修改原值、含slice/map/chan等时必须用指针。

不一定快,小结构体值传递通常更快或持平,大结构体(≥256 字节)才明显受益于指针传递。
什么时候 byValue 比 byPointer 更快?
现代 CPU 缓存对紧凑数据友好,而指针多一次解引用(*p),还可能触发逃逸到堆上。实测中,≤16 字节的结构体(比如 struct{X, Y int} 或 time.Time)走值传递反而更优:
-
string传值只拷贝 16 字节 header(ptr/len/cap),别把它当“大对象”误判 - 高频路径(如 HTTP 中间件)中,值传递省去解引用延迟,热点代码更易被内联
- 字段少、无指针包装类型(如
[]byte、map[string]int)的小结构体,栈分配稳定,不逃逸
什么时候必须用 *T 而不是 T?
性能只是因素之一;语义和正确性往往更关键:
- 需要修改原值:比如
func (s *User) SetName(n string),值接收者改的是副本 - 结构体含
[]byte、map、chan等字段:即使整体很小,拷贝 descriptor 也可能引发意外堆分配 - 结构体超过 2 个 machine word(>16 字节)且被高频调用:例如
struct{ID int64; Name string; Created time.Time; Tags []string},值传递每次复制至少 40+ 字节 + slice 头 - 结构体内含
sync.Mutex等不可拷贝字段:编译直接报错,只能用指针
怎么验证你当前的写法是否合理?
别猜,跑基准测试 + 查逃逸分析:
- 写两个 benchmark:
BenchmarkByValue和BenchmarkByPointer,确保用b.ReportAllocs()统计堆分配 - 禁用内联:加
-gcflags="-l"防止编译器抹平调用开销 - 防止优化消除:把函数返回值赋给全局变量,如
globalResult = f(s) - 查逃逸:用
go build -gcflags="-m -l" main.go,重点看 “moved to heap” 提示——如果值传递也逃逸了,那指针未必带来收益
最常被忽略的一点:逃逸分析的影响远大于“传值 or 传指针”的表面选择——一个局部变量只要被取地址并传出作用域,就注定上堆,此时再纠结拷贝大小意义不大。











