Reputation: 2239
The following code shows two benchmarks. The first one creates a struct by value in each iteration, while the second one does use a pointer to the struct.
Why is the latter 20x slower ?? I know about GC issues with GoLang, but shouldn't escape analysis handle those situations ?
I'm using go1.4beta1, but 1.3.3 gave me the [same - wrong] different results.
Any idea ?
package main
import "testing"
type Adder struct {
vals []int
}
func (a *Adder) add() int {
return a.vals[0] + a.vals[1]
}
func BenchmarkWithoutPointer(b *testing.B) {
accum := 0
for i := 0; i < b.N; i++ {
adder := Adder{[]int{accum, i}}
accum = adder.add()
}
_ = accum
}
func BenchmarkWithPointer(b *testing.B) {
accum := 0
for i := 0; i < b.N; i++ {
adder := &Adder{[]int{accum, i}}
accum = adder.add()
}
_ = accum
}
Benchmark go1.4.1:
$ go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkWithoutPointer 1000000000 2.92 ns/op
BenchmarkWithPointer 30000000 57.8 ns/op
ok github.com/XXXXXXXXXX/bench/perf 5.010s
Benchmark go1.3.3:
testing: warning: no tests to run
PASS
BenchmarkWithoutPointer 500000000 7.89 ns/op
BenchmarkWithPointer 50000000 37.5 ns/op
ok
EDIT:
Conclusion:
As Ainar-G said, the []int does escape to heap in the second benchmark. After reading a bit more about 1.4beta1 it seems, that new write barriers are introduced when accessing the heap caused by the new GC plans. But raw execution seems to have increased. Looking forward to 1.5 =).
Upvotes: 7
Views: 2884
Reputation: 87
running the exact code from the original post with go1.16.7 the pointer version is now about the same speed (slightly faster)
$ go test -bench=.
goos: linux
goarch: amd64
pkg: example.com/x
cpu: Intel(R) Core(TM) i7-9850H CPU @ 2.60GHz
BenchmarkWithoutPointer-12 945450447 1.212 ns/op
BenchmarkWithPointer-12 965921562 1.199 ns/op
PASS
ok example.com/x 2.562s
so the compiler has gotten smarter since OP posed his question :)
Upvotes: 2
Reputation: 36199
Running the benchmark with the -m
gcflag gives the possible answer:
./main_test.go:16: BenchmarkWithoutPointer []int literal does not escape
(...)
./main_test.go:25: []int literal escapes to heap
Your []int
in the second example escapes to heap, which is slower than stack. If you use separate x
and y
fields for your arguments instead of a slice
type Adder struct {
x, y int
}
func (a *Adder) add() int {
return a.x + a.y
}
the benchmark shows the expected behaviour:
BenchmarkWithoutPointer 1000000000 2.27 ns/op
BenchmarkWithPointer 2000000000 1.98 ns/op
Upvotes: 12