Repository: go
Issue: context: Remove allocation discussion from WithValue documentation
Labels: Documentation,NeedsDecision

This is a proposed package documentation change. I'm happy to submit a code change with this update, if it makes sense. I took the liberty of abbreviating the questions in the template.


### What version of Go are you using (`go version`)?

Documentation in the [latest source of context.go (commit d6ffc1d8394d6f6420bb92d79d320da88720fbe0)](https://github.com/golang/go/blob/d6ffc1d8394d6f6420bb92d79d320da88720fbe0/src/context/context.go)


### What does the current documentation say

WithValue: "To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}. Alternatively, exported context key variables' static type should be a pointer or interface."

Current documentation: https://tip.golang.org/pkg/context/#WithValue
Code: https://github.com/golang/go/blob/d6ffc1d8394d6f6420bb92d79d320da88720fbe0/src/context/context.go#L476-L479


### What should it say

Those two sentences should be removed. With Go >= 1.9, it no longer matters. To verify, I ran the following test with different versions of Go on a VM with a command like:

```docker run --workdir=/wtf/test -v $HOME:/wtf -ti --rm golang:1.12 go test -bench=. .```

I tested each release from 1.12 through to 1.8. With version 1.8, this mattered a lot. It no longer does. From the output below, you can see that using an `int` key is slower, but does not allocate. The other choices (`interface{}`, pointer, custom string), all appear to be equivalent.

I think it would simplify the package documentation to omit this.

This was previously changed after the discussion in https://github.com/golang/go/issues/17826 . My test is based on that one.


#### Go 1.12 

```
goos: linux
goarch: amd64
BenchmarkInterfaceKey-2      	1000000000	         2.64 ns/op	       0 B/op	       0 allocs/op
BenchmarkIntKey-2            	300000000	         4.56 ns/op	       0 B/op	       0 allocs/op
BenchmarkStringKey-2         	1000000000	         2.64 ns/op	       0 B/op	       0 allocs/op
BenchmarkCustomStringKey-2   	1000000000	         2.64 ns/op	       0 B/op	       0 allocs/op
BenchmarkEmptyStructKey-2    	1000000000	         2.63 ns/op	       0 B/op	       0 allocs/op
BenchmarkPtrKey-2            	1000000000	         2.63 ns/op	       0 B/op	       0 allocs/op
```

#### Go 1.9

```
goos: linux
goarch: amd64
BenchmarkInterfaceKey-2      	1000000000	         2.66 ns/op	       0 B/op	       0 allocs/op
BenchmarkIntKey-2            	300000000	         5.28 ns/op	       0 B/op	       0 allocs/op
BenchmarkStringKey-2         	1000000000	         2.64 ns/op	       0 B/op	       0 allocs/op
BenchmarkCustomStringKey-2   	1000000000	         2.63 ns/op	       0 B/op	       0 allocs/op
BenchmarkEmptyStructKey-2    	1000000000	         2.66 ns/op	       0 B/op	       0 allocs/op
BenchmarkPtrKey-2            	1000000000	         2.67 ns/op	       0 B/op	       0 allocs/op
```


#### Go 1.8

```
BenchmarkInterfaceKey-2      	300000000	         4.37 ns/op	       0 B/op	       0 allocs/op
BenchmarkIntKey-2            	50000000	        30.6 ns/op	       8 B/op	       1 allocs/op
BenchmarkStringKey-2         	30000000	        47.6 ns/op	      16 B/op	       1 allocs/op
BenchmarkCustomStringKey-2   	30000000	        47.6 ns/op	      16 B/op	       1 allocs/op
BenchmarkEmptyStructKey-2    	100000000	        15.1 ns/op	       0 B/op	       0 allocs/op
BenchmarkPtrKey-2            	300000000	         4.57 ns/op	       0 B/op	       0 allocs/op
```


### Test code

```go
package test

import (
	"context"
	"testing"
)

type key interface{}

var keyInterface key = 0

type keyIntType int

var keyInt keyIntType = 0

type List struct{}

type emptyStruct struct{}

var emptyStructKey = emptyStruct{}

const stringKey = "somestring"

type customStringKeyT string

const customStringKey = customStringKeyT("customstring")

var someString = "hello"
var ptrKey *string = &someString

func BenchmarkInterfaceKey(b *testing.B) {
	b.ReportAllocs()
	ctx := context.Background()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ctx.Value(keyInterface)
		}
	})
}

func BenchmarkIntKey(b *testing.B) {
	b.ReportAllocs()
	ctx := context.Background()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ctx.Value(keyInt)
		}
	})
}

func BenchmarkStringKey(b *testing.B) {
	b.ReportAllocs()
	ctx := context.Background()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ctx.Value(stringKey)
		}
	})
}

func BenchmarkCustomStringKey(b *testing.B) {
	b.ReportAllocs()
	ctx := context.Background()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ctx.Value(customStringKey)
		}
	})
}

func BenchmarkEmptyStructKey(b *testing.B) {
	b.ReportAllocs()
	ctx := context.Background()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ctx.Value(emptyStructKey)
		}
	})
}

func BenchmarkPtrKey(b *testing.B) {
	b.ReportAllocs()
	ctx := context.Background()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ctx.Value(ptrKey)
		}
	})
}
```