Skip to content

Instantly share code, notes, and snippets.

@xogeny
Created January 27, 2015 18:12
Show Gist options
  • Save xogeny/c8dfd0c4742aee502926 to your computer and use it in GitHub Desktop.
Save xogeny/c8dfd0c4742aee502926 to your computer and use it in GitHub Desktop.
A different append benchmark for Golang
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)
}
}
@xogeny
Copy link
Author

xogeny commented Jan 27, 2015

In this case, the results of go test -bench=. are a little different than the previous Gist:

PASS
BenchmarkAppend                     500000        5766 ns/op
BenchmarkAppendAlloc                5000000        308 ns/op
BenchmarkAppendAllocInline          5000000        511 ns/op
BenchmarkAppendAllocInlineNoFunc    5000000        604 ns/op
BenchmarkCopy                       5000000        308 ns/op

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 the AppendAlloc version and the Copy version are nearly identical in performance.

@xogeny
Copy link
Author

xogeny commented Jan 27, 2015

This is a followup to a previous gist on the same benchmarks.

@xogeny
Copy link
Author

xogeny commented Jan 27, 2015

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

@zigo101
Copy link

zigo101 commented Jun 27, 2017

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