Go: Should I Use a Pointer instead of a Copy of my Struct?

Vincent
A Journey With Go
Published in
5 min readMay 12, 2019

--

Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

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…

--

--