It is common to use integer types as keys in maps due low memeory allocation leading to better performance. For example int8 occupies a single byte of memory. An empty struct in golang have a minimum size of zero bytes, but they may have a size greater than zero due to padding. The size of an empty struct in Go is implementation-dependent, and it is usually 1 byte or larger to ensure that each instance of a struct has a unique memory address.
package main
import (
"fmt"
"unsafe"
)
type EmptyStruct struct{}
func main() {
size := unsafe.Sizeof(EmptyStruct{})
fmt.Println(size) // Output: 0
}
We can therfore assume in terms of performance and memory use, using empty structs as keys for context values in Go can be a more efficient choice compared to using int8 values.
We can confirm this using a benchmark test.
package main
import (
"context"
"testing"
)
func insertGetCtx(key, value interface{}) {
ctx := context.WithValue(context.Background(), key, value)
_ = ctx.Value(key)
}
func BenchmarkInsertGetCtx(b *testing.B) {
val := "some-value"
b.Run("string-key", func(b *testing.B) {
for i := 0; i < b.N; i++ {
type ctxKey string
const myKey ctxKey = "string-key"
insertGetCtx(myKey, val)
}
})
b.Run("int-key", func(b *testing.B) {
for i := 0; i < b.N; i++ {
type ctxKey int
const myKey ctxKey = 1
insertGetCtx(myKey, val)
}
})
b.Run("empty-struct-key", func(b *testing.B) {
for i := 0; i < b.N; i++ {
type ctxKey struct{}
insertGetCtx(ctxKey{}, val)
}
})
}
The results of the benchmark form 10,000,000 iterations show our earlier aassumption.
go test -bench=. -benchtime=10000000x ✔
goos: linux
goarch: amd64
pkg: test
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkInsertGetCtx/string-key-8 10000000 90.75 ns/op
BenchmarkInsertGetCtx/int-key-8 10000000 83.70 ns/op
BenchmarkInsertGetCtx/empty-struct-key-8 10000000 79.37 ns/op
PASS
ok test 2.543s
This for this, great summary. Why are you creating the type in every iteration?