Structs Vs Pointers

This repository tries to compare the performance of structs and pointers across various scenarios.

Note - This test is run against Go version 1.21.3 with 1 cpu.

Benchmarking Results

  • No Stack Without Return - This is the most common scenario where the object, either struct or its pointer is used within a single method with the object being withing the function itself i.e. no return of object.
  • No Stack With Return - This is the most common scenario where the object, either struct or its pointer is used within a single method along with returning the value.
  • Basic Stack - This is the scenario where the object, either struct or its pointer is passed down multiple functions.
  • Arrays - This is the scenario where the object, either struct or its pointer is part of an array and is passed down multiple functions.
  • Maps - This is the scenario where the object, either struct or its pointer is part of a map and is passed down multiple functions.

No Stack Without Return

This is the most common scenario where the object, either struct or its pointer is used within a single method with the object being withing the function itself i.e. no return of object.

/Users/shubham.sinha/sdk/go1.21.3/bin/go test -v ./... -bench . -run ^$ -benchmem -cpu 1
goos: darwin
goarch: amd64
pkg: github.com/sinhashubham95/structs-vs-pointers/nostackwithoutreturn
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkS10fByValue
BenchmarkS10fByValue   	1000000000	         0.3053 ns/op	       0 B/op	       0 allocs/op
BenchmarkS10fByPointer
BenchmarkS10fByPointer 	1000000000	         0.2872 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByValue
BenchmarkS2fByValue    	1000000000	         0.3149 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByPointer
BenchmarkS2fByPointer  	1000000000	         0.2905 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByValue
BenchmarkS5fByValue    	1000000000	         0.3412 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByPointer
BenchmarkS5fByPointer  	1000000000	         0.2850 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/sinhashubham95/structs-vs-pointers/nostackwithoutreturn	       2.367s

Within a function, both are equally treated in all aspects of performance, but as we all know using by reference without any extra returns is unnecessary. So it's preferred to work within a function by value.

No Stack With Return

This is the most common scenario where the object, either struct or its pointer is used within a single method along with returning the value.

/Users/shubham.sinha/sdk/go1.21.3/bin/go test -v ./... -bench . -run ^$ -benchmem -cpu 1
goos: darwin
goarch: amd64
pkg: github.com/sinhashubham95/structs-vs-pointers/nostackwithreturn
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkS10fByValue
BenchmarkS10fByValue   	134305704	         9.561 ns/op	       0 B/op	       0 allocs/op
BenchmarkS10fByPointer
BenchmarkS10fByPointer 	1000000000	         0.3030 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByValue
BenchmarkS2fByValue    	1000000000	         0.2883 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByPointer
BenchmarkS2fByPointer  	1000000000	         0.2863 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByValue
BenchmarkS5fByValue    	237990206	         5.632 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByPointer
BenchmarkS5fByPointer  	1000000000	         0.2560 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/sinhashubham95/structs-vs-pointers/nostackwithreturn	               5.625s

When the object is returned from the function, structs with lesser fields can be allocated withing the stack frame, that's why it performs similar to returning a pointer of the same. But with structs having more fields, when returned by value, it escapes the stack frame, resulting in degraded performance. So it's preferred to return a struct by pointer.

Basic Stack

This is the scenario where the object, either struct or its pointer is passed down multiple functions.

/Users/shubham.sinha/sdk/go1.21.3/bin/go test -v ./... -bench . -run ^$ -benchmem -cpu 1
goos: darwin
goarch: amd64
pkg: github.com/sinhashubham95/structs-vs-pointers/stack
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkS10fByValue
BenchmarkS10fByValue   	63777744	        18.56 ns/op	       0 B/op	       0 allocs/op
BenchmarkS10fByPointer
BenchmarkS10fByPointer 	1000000000	        0.2970 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByValue
BenchmarkS2fByValue    	1000000000	        0.2998 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByPointer
BenchmarkS2fByPointer  	1000000000	        0.2845 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByValue
BenchmarkS5fByValue    	89151668	        11.95 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByPointer
BenchmarkS5fByPointer  	1000000000	        0.2558 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/sinhashubham95/structs-vs-pointers/stack	       5.856s

Similar to the section above, whenever structs have to be returned, it's preferred to use pointers.

Arrays(Slices)

This is the scenario where the object, either struct or its pointer is part of an array and is passed down multiple functions.

/Users/shubham.sinha/sdk/go1.21.3/bin/go test -v ./... -bench . -run ^$ -benchmem -cpu 1
goos: darwin
goarch: amd64
pkg: github.com/sinhashubham95/structs-vs-pointers/arrays
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkS10fByValue
BenchmarkS10fByValue   	54680168	        21.44 ns/op	       0 B/op	       0 allocs/op
BenchmarkS10fByPointer
BenchmarkS10fByPointer 	40205076	        32.38 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByValue
BenchmarkS2fByValue    	1000000000	        0.2780 ns/op	       0 B/op	       0 allocs/op
BenchmarkS2fByPointer
BenchmarkS2fByPointer  	157222998	        6.792 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByValue
BenchmarkS5fByValue    	1000000000	        0.3046 ns/op	       0 B/op	       0 allocs/op
BenchmarkS5fByPointer
BenchmarkS5fByPointer  	64820839	        17.53 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/sinhashubham95/structs-vs-pointers/arrays           7.374s

By default, the Go compiler passes the arrays(slices) as a slice header struct, so if the underlying struct type is its pointer, it skips the optimisation the Go compiler does to contain it within the stack frame. So it's always preferred to use the underlying type as the struct value, rather than a pointer.

Maps

This is the scenario where the object, either struct or its pointer is part of a map and is passed down multiple functions.

/Users/shubham.sinha/sdk/go1.21.3/bin/go test -v ./... -bench . -run ^$ -benchmem -cpu 1
goos: darwin
goarch: amd64
pkg: github.com/sinhashubham95/structs-vs-pointers/maps
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkS10fByValue
BenchmarkS10fByValue   	 1296307	       948.6 ns/op	     320 B/op	       2 allocs/op
BenchmarkS10fByPointer
BenchmarkS10fByPointer 	 3258984	       365.7 ns/op	     320 B/op	       2 allocs/op
BenchmarkS2fByValue
BenchmarkS2fByValue    	 6066308	       207.0 ns/op	     0 B/op	       0 allocs/op
BenchmarkS2fByPointer
BenchmarkS2fByPointer  	 4198629	       294.0 ns/op	     64 B/op	       2 allocs/op
BenchmarkS5fByValue
BenchmarkS5fByValue    	 4554530	       288.8 ns/op	     0 B/op	       0 allocs/op
BenchmarkS5fByPointer
BenchmarkS5fByPointer  	 2706584	       394.4 ns/op	     160 B/op	       2 allocs/op
PASS
ok  	github.com/sinhashubham95/structs-vs-pointers/maps	     10.182s

By default, the Go compiler passes the maps by pointer, so if the underlying struct type is its pointer, it skips the optimisation the Go compiler does to contain it within the stack frame. So it's always preferred to use the underlying type as the struct value, rather than a pointer.