Go: Should I Use a Pointer instead of a Copy of my Struct?
--
For many Go developers, the systematic use of pointers to share structs instead of the copy itself seems the best option in terms of performance.
In order to understand the impact of using a pointer rather than a copy of the struct, we will review two use cases.
Intensive allocation of data
Let’s take a simple example of when you want to share a struct for its values:
type S struct {
a, b, c int64
d, e, f string
g, h, i float64
}
Here is a basic struct that can be shared by copy or by pointer:
func byCopy() S {
return S{
a: 1, b: 1, c: 1,
e: "foo", f: "foo",
g: 1.0, h: 1.0, i: 1.0,
}
}
func byPointer() *S {
return &S{
a: 1, b: 1, c: 1,
e: "foo", f: "foo",
g: 1.0, h: 1.0, i: 1.0,
}
}
Based on those 2 methods, we can now write 2 benchmarks, one where the struct is passed by copy:
func BenchmarkMemoryStack(b *testing.B) {
var s S
f, err := os.Create("stack.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
s = byCopy()
}
trace.Stop()
b.StopTimer()
_ = fmt.Sprintf("%v", s.a)
}
And another one, very similar, when it is passed by pointer:
func BenchmarkMemoryHeap(b *testing.B) {
var s *S
f, err := os.Create("heap.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
s = byPointer()
}
trace.Stop()
b.StopTimer()
_ = fmt.Sprintf("%v", s.a)
}
Let’s run the benchmarks:
go test ./... -bench=BenchmarkMemoryHeap -benchmem -run=^$ -count=10 > head.txt && benchstat head.txt
go test ./... -bench=BenchmarkMemoryStack -benchmem -run=^$ -count=10 > stack.txt && benchstat stack.txt
Here are the stats:
name time/op
MemoryHeap-4 75.0ns ± 5%name alloc/op
MemoryHeap-4 96.0B ± 0%name allocs/op…