Created
January 27, 2015 18:12
-
-
Save xogeny/c8dfd0c4742aee502926 to your computer and use it in GitHub Desktop.
A different append benchmark for Golang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package copy_vs_append | |
import ( | |
"testing" | |
) | |
func buildArray(n int64) []int64 { | |
ret := make([]int64, n, n) | |
var i int64 | |
for i = 0; i < n; i++ { | |
ret[i] = i | |
} | |
return ret | |
} | |
func checkArray(y []int64, t *testing.T) { | |
if len(y) != 1000 { | |
t.Fatalf("Expected len(y) to be 1000 but was %d", len(y)) | |
} | |
for i := int64(0); i < int64(1000); i++ { | |
if y[i] != i { | |
t.Fatalf("Expected y[%d] = %d but was %d", i, i, y[i]) | |
} | |
} | |
} | |
func TestCopy(t *testing.T) { | |
existing := buildArray(1000) | |
y := doCopy(existing, true, false) | |
checkArray(y, t) | |
} | |
func TestAppend(t *testing.T) { | |
existing := buildArray(1000) | |
y := doCopy(existing, false, false) | |
checkArray(y, t) | |
} | |
func TestAppendAlloc(t *testing.T) { | |
existing := buildArray(1000) | |
y := doCopy(existing, false, true) | |
checkArray(y, t) | |
} | |
func doCopy(src []int64, useCopy bool, preAlloc bool) []int64 { | |
var y []int64 | |
if useCopy { | |
y = make([]int64, 1000, 1000) | |
copy(y, src) | |
} else { | |
var init []int64 | |
if preAlloc { | |
init = make([]int64, 0, 1000) | |
} else { | |
init = []int64{} | |
} | |
y = append(init, src...) | |
} | |
return y | |
} | |
func BenchmarkAppend(b *testing.B) { | |
existing := buildArray(1000) | |
for i := 0; i < b.N; i++ { | |
doCopy(existing, false, false) | |
} | |
} | |
func BenchmarkAppendAlloc(b *testing.B) { | |
existing := buildArray(1000) | |
for i := 0; i < b.N; i++ { | |
doCopy(existing, false, true) | |
} | |
} | |
func BenchmarkAppendAllocInline(b *testing.B) { | |
existing := buildArray(1000) | |
for i := 0; i < b.N; i++ { | |
var init []int64 | |
init = make([]int64, 0, 1000) | |
_ = append(init, existing...) | |
} | |
} | |
func BenchmarkAppendAllocInlineNoFunc(b *testing.B) { | |
existing := make([]int64, 1000, 1000) | |
for i := 0; i < b.N; i++ { | |
var init []int64 | |
init = make([]int64, 0, 1000) | |
_ = append(init, existing...) | |
} | |
} | |
func BenchmarkCopy(b *testing.B) { | |
existing := buildArray(1000) | |
for i := 0; i < b.N; i++ { | |
doCopy(existing, true, true) | |
} | |
} |
This is a followup to a previous gist on the same benchmarks.
A follow up, I modified this to allow arrays of different sizes (1000 vs 100000) and I did a b.ResetTimer
just before the loop to ignore the cost of creating the array.
Results were:
PASS
BenchmarkAppend 10000 210092 ns/op
BenchmarkAppendAlloc 10000 203140 ns/op
BenchmarkAppendAllocInline 10000 204048 ns/op
BenchmarkAppendAllocInlineNoFunc 5000 258291 ns/op
BenchmarkCopy 10000 207800 ns/op
The compiler will make some optimization if it finds a local variable is not used actually.
(In only realize this recently)
So it is best to use global variables as the inputs and outputs of tested code.
package copy_vs_append
import (
"testing"
)
func buildArray(n int64) []int64 {
ret := make([]int64, n, n)
var i int64
for i = 0; i < n; i++ {
ret[i] = i
}
return ret
}
func checkArray(y []int64, t *testing.T) {
if len(y) != 1000 {
t.Fatalf("Expected len(y) to be 1000 but was %d", len(y))
}
for i := int64(0); i < int64(1000); i++ {
if y[i] != i {
t.Fatalf("Expected y[%d] = %d but was %d", i, i, y[i])
}
}
}
var existing = buildArray(1000)
var y []int64
func TestCopy(t *testing.T) {
y = doCopy(existing, true, false)
checkArray(y, t)
}
func TestAppend(t *testing.T) {
y = doCopy(existing, false, false)
checkArray(y, t)
}
func TestAppendAlloc(t *testing.T) {
y = doCopy(existing, false, true)
checkArray(y, t)
}
func doCopy(src []int64, useCopy bool, preAlloc bool) []int64 {
var y []int64
if useCopy {
y = make([]int64, 1000, 1000)
copy(y, src)
} else {
var init []int64
if preAlloc {
init = make([]int64, 0, 1000)
} else {
init = []int64{}
}
y = append(init, src...)
}
return y
}
func BenchmarkAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
y = doCopy(existing, false, false)
}
}
func BenchmarkAppendAlloc(b *testing.B) {
for i := 0; i < b.N; i++ {
y = doCopy(existing, false, true)
}
}
func BenchmarkAppendAllocInline(b *testing.B) {
for i := 0; i < b.N; i++ {
var init []int64
init = make([]int64, 0, 1000)
y = append(init, existing...)
}
}
func BenchmarkAppendAllocInlineNoFunc(b *testing.B) {
for i := 0; i < b.N; i++ {
var init []int64
init = make([]int64, 0, 1000)
y = append(init, existing...)
}
}
func BenchmarkCopy(b *testing.B) {
for i := 0; i < b.N; i++ {
y = doCopy(existing, true, true)
}
}
Benchmark result:
BenchmarkAppend-4 500000 2118 ns/op
BenchmarkAppendAlloc-4 1000000 2254 ns/op
BenchmarkAppendAllocInline-4 1000000 2260 ns/op
BenchmarkAppendAllocInlineNoFunc-4 1000000 2259 ns/op
BenchmarkCopy-4 1000000 2264 ns/op
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In this case, the results of
go test -bench=.
are a little different than the previous Gist:Compared to the previous benchmark, the time taken per operation is far less. What happened?
Well first, the allocation of the array to be copied is now factored out of the loop. But that benefit somehow missed the
BenchmarkAppend
version completely. But ignoring that, we see a result that is consistent with the previous versions which is that theAppendAlloc
version and theCopy
version are nearly identical in performance.