值传递在小结构体(≤16字节)时更快,因避免解引用和逃逸;大结构体(≥256字节)或需修改原值、含引用字段时才应传指针;验证须结合基准测试与逃逸分析。

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











