值传递适合小结构体(64字节);基准测试需用go test -bench,禁用内联、稳定gc,并用b.reportallocs检测堆分配。

Go 中结构体传参:值传递和指针传递到底差多少
差别取决于结构体大小和使用方式。小于 16 字节的小结构体(比如 Point{int, int})值传递通常更快;超过 64 字节后,指针传递几乎总是更优,尤其在频繁调用或嵌套深的场景里。
怎么测出真实差异:用 go test -bench 而不是手写循环
手动计时误差大,且 Go 的编译器可能优化掉未使用的参数或内联简单函数,导致结果失真。基准测试能禁用内联、稳定 GC 状态,并跑足够多轮取平均值。
- 必须用
func BenchmarkXxx(b *testing.B)形式,且每次迭代都要调用目标函数 - 避免在
Benchmark函数里声明大变量——它会被算进耗时,干扰结构体传参本身的开销 - 用
b.ReportAllocs()观察是否意外触发堆分配(值传递大结构体可能逃逸到堆) - 示例片段:
func BenchmarkStructValue(b *testing.B) { s := BigStruct{...} for i := 0; i < b.N; i++ { consumeValue(s) // 注意:这里 s 是复制的 } }go tool compile -S看汇编:确认结构体真被复制了没Go 编译器对小结构体常做寄存器传参或栈内紧凑拷贝,对大结构体则生成显式的
MOVQ/MOVO块拷贝指令。不看汇编,光靠“结构体很大”就加*,反而可能破坏 CPU 缓存局部性。- 运行
go tool compile -S main.go | grep -A5 'consumeValue',找是否有大块内存移动指令 - 如果函数被内联,汇编里看不到调用,需加
//go:noinline注释强制不内联 - 32 字节以内的结构体,在 amd64 上大概率走
AX/DX/R8/R9寄存器传,基本无额外开销
别只盯着“传什么”,先看“怎么用”:逃逸分析才是关键分水岭
即使你写了值传递,如果结构体字段被取地址、传给 goroutine、或作为接口值存储,它就会逃逸到堆——这时复制成本反而是次要的,GC 压力和内存延迟才真正拖慢性能。
立即学习“go语言免费学习笔记(深入)”;
- 用
go run -gcflags="-m -l" main.go检查结构体是否逃逸 - 指针传递不等于一定上堆:栈上分配的结构体,其地址仍可安全传指针(只要生命周期可控)
- 常见坑:把大结构体直接塞进
map[string]BigStruct,读写时会反复复制;换成map[string]*BigStruct后,注意 nil 检查和生命周期管理
*,得先看它是否逃逸、是否高频调用、是否跨 goroutine 共享。盲目统一用指针,可能让小结构体失去寄存器优化,还增加一次解引用;死守值传递,又可能在日志、序列化等场景里引发大量内存拷贝。这两条路的边界,不在字节数上,而在逃逸分析结果和 profile 数据里。 - 运行











