To make slice append operation faster we need to allocate enough capacity. There\'s two ways to append slice, Here is the code:
func BenchmarkSliceAppend(b *t
a[i] = i
simply assigns the value i
to a[i]
. This is not appending, it's just a simple assignment.
Now the append:
a = append(a, i)
In theory the following happens:
This calls the builtin append() function. For that, it first has to copy the a
slice (slice header, backing array is not part of the header), and it has to create a temporary slice for the variadic parameter which will contain the value i
.
Then it has to reslice a
if it has enough capacity (it has in your case) like a = a[:len(a)+1]
- which involves assigning the new slice to a
inside the append()
.
(If a
would not have big enough capacity to do the append "in-place", a new array would have to be allocated, content from slice copied, and then the assign / append be executed - but it is not the case here.)
Then assigns i
to a[len(a)-1]
.
Then returns the new slice from append()
, and this new slice is assigned to the local variable a
.
A lot of things happen here compared to a simple assignment. Even if many of these steps are optimized and / or inlined, as a minimum addition to assigning i
to an element of the slice, the local variable a
of slice type (which is a slice header) has to be updated in each cycle of the loop.
Recommended reading: The Go Blog: Arrays, slices (and strings): The mechanics of 'append'
It seems like some improvements of Go compiler or runtime have been introduced since this question has been posted, so now (Go 1.10.1
) there is no significant difference between append
and direct assignment by index.
Also, I had to change your benchmarks slightly because of OOM panics.
package main
import "testing"
var result []int
const size = 32
const iterations = 100 * 1000 * 1000
func doAssign() {
data := make([]int, size)
for i := 0; i < size; i++ {
data[i] = i
}
result = data
}
func doAppend() {
data := make([]int, 0, size)
for i := 0; i < size; i++ {
data = append(data, i)
}
result = data
}
func BenchmarkAssign(b *testing.B) {
b.N = iterations
for i := 0; i < b.N; i++ {
doAssign()
}
}
func BenchmarkAppend(b *testing.B) {
b.N = iterations
for i := 0; i < b.N; i++ {
doAppend()
}
}
Results:
➜ bench_slice_assign go test -bench=Bench .
goos: linux
goarch: amd64
BenchmarkAssign-4 100000000 80.9 ns/op
BenchmarkAppend-4 100000000 81.9 ns/op
PASS
ok _/home/isaev/troubles/bench_slice_assign 16.288s