Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid alloc op when assign a string to []interface{} element #32305

Closed
tnclong opened this issue May 29, 2019 · 2 comments
Closed

Avoid alloc op when assign a string to []interface{} element #32305

tnclong opened this issue May 29, 2019 · 2 comments

Comments

@tnclong
Copy link
Contributor

tnclong commented May 29, 2019

What version of Go are you using (go version)?

go version go1.11.1 darwin/amd64

Does this issue reproduce with the latest release?

Not have a check.

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/*/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/54/k5ygh9s15rzbjk3m1_4qmvy40000gn/T/go-build237216686=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I write a program that need to previous append and tail append string to a []interface{}{}.

const (
	tail = "tail"
)

func BenchmarkArroundHasHead(b *testing.B) {
	head := "head assign to interface{} add one alloc"
	benchmarkArround(b, &head)
}
func BenchmarkArroundHeadEmpty(b *testing.B) {
	// empty head add to interface{} not add one alloc
	head := ""
	benchmarkArround(b, &head)
}

func BenchmarkArroundHeadNil(b *testing.B) {
	benchmarkArround(b, nil)
}

func benchmarkArround(b *testing.B, head *string) {
	a := []interface{}{"1", 2}
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		aa := make([]interface{}, len(a)+2)
		if head != nil {
			aa[0] = *head
		}
		copy(aa[1:], a)
		// const tail add to interface{} not add one alloc
		aa[len(a)+1] = tail
	}
}

What did you expect to see?

BenchmarkArroundHasHead-8 is 1 allocs/op.

What did you see instead?

BenchmarkArroundHasHead-8 is 2 allocs/op that make code slower.

The head is dynamic but able to make a complex process before exec Arround() method.
In other words, process head one time, use it forever.

Do we have some functions in reflect, unsafe or other packages that make the alloc not necessary? Or work as const? I see const not add one alloc, so I ask this question.

Benchmark

$ go test -bench=BenchmarkArround -benchmem -count=1 --test.run="xxxx"
goos: darwin
goarch: amd64
pkg: github.com/tnclong/berry
BenchmarkArroundHasHead-8     	20000000	        93.2 ns/op	      80 B/op	       2 allocs/op
BenchmarkArroundHeadEmpty-8   	20000000	        68.2 ns/op	      64 B/op	       1 allocs/op
BenchmarkArroundHeadNil-8     	20000000	        64.8 ns/op	      64 B/op	       1 allocs/op
PASS
ok  	github.com/tnclong/berry	4.775s
@rsc
Copy link
Contributor

rsc commented May 29, 2019

No, we don't have any way to optimize that away. An interface holds a single data pointer. To store a string in an interface it must be represented internally as a pointer to that string header. For string constants we have a string header in read-only memory, the address of which can be stored in the interface. For arbitrary strings, we need a new string header that won't be modified underfoot, and that means a string-header-sized allocation.

Of course, if you are converting the same string into an interface{} multiple times, you can store it into an interface{} once and reuse that interface value, cutting down to just one allocation before a loop instead of one inside a loop. If you changed the argument from head \*string to head interface{} the benchmark would stop allocating in the loop.

@rsc rsc closed this as completed May 29, 2019
@tnclong
Copy link
Contributor Author

tnclong commented May 30, 2019

@rsc Thanks. This is good to me.

Also Thanks for your comments and articles in Internet.

type R struct {
	r string

	// https://github.com/golang/go/issues/32305#issuecomment-497051905
	ri interface{}
}

@golang golang locked and limited conversation to collaborators May 29, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants