Bencher is available on the Github marketplace!

Slices: make vs append

Introduction

An idiom to create slices and maps is to use make whose signature is

func make(t Type, size ...IntegerType) Type

which by interpretation says the second argument size defines the size of the slice, while an optional extra value is to use the alternative capacity that MUST NOT BE LESS THAN THE SIZE Thus bringing us to this signature:

make(<T>, size, capacity)

In this performance clinic series, we shall focus on slices firstly! Slices are the most common representation of items in a collection that can be iterated over, used collectively and searched. The million dollar question when creating the very common slice is: what’s the most performant way of creating a slice? make([]<T>, n)? make([]<T>, 0, n)? or a plain append()?

<a class=“btn btn-light " onclick=“location.href='/starting'”

Our initial program will start off as a simple function whose purpose is to create a slice of certain size and return it:

func makeIt(n int) (data []int) {
	for i := 0; i < n; i++ {
		data = append(data, i)
	}
	return data
}

Set up benchmarking

To set up our shoot-out for benchmarking and solid data backed by numbers, we need to do the following:

Benchmark the code

To figure out how it performs, let’s start by benchmarking it.

package makestyles

import "testing"

var sink interface{}

func benchmarkMake(b *testing.B, size int) {
	for i := 0; i < b.N; i++ {
		sink = makeIt(size)
	}
	if sink == nil {
		b.Fatal("Benchmark didn't run")
	}
}

func BenchmarkMake10(b *testing.B) {
	benchmarkMake(b, 10)
}

func BenchmarkMake100(b *testing.B) {
	benchmarkMake(b, 100)
}

func BenchmarkMake1000(b *testing.B) {
	benchmarkMake(b, 1000)
}

func BenchmarkMake10000(b *testing.B) {
	benchmarkMake(b, 10000)
}

func BenchmarkMake100000(b *testing.B) {
	benchmarkMake(b, 100000)
}

func BenchmarkMake1000000(b *testing.B) {
	benchmarkMake(b, 1000000)
}

Commit the result

Having successfully added Bencher to our repository in two clicks, we mailed the first change. That made Bencher spawn the following:

make with size=0 capacity=n

A common pattern is to invoke make([]T, 0, n) but the question here is: is it performant? How much better? To find out these answers, we create a branch perhaps called make-with-capacity-n and then make this modification:

diff --git a/makestyles/bench_test.go b/makestyles/bench_test.go
index 7239d97..5e8305e 100644
--- a/makestyles/bench_test.go
+++ b/makestyles/bench_test.go
@@ -3,6 +3,7 @@ package makestyles
 import "testing"
 
 func makeIt(n int) (data []int) {
+	data = make([]int, 0, n)
 	for i := 0; i < n; i++ {
 		data = append(data, i)
 	}

Results

After Bencher runs, we can look at our Github check:

which when clicked on demonstrates the results:

and then navigate to the Bencher details URL which looks like:

We now know what’s up! That actually improved things significantly!!

However, is that all that we can do? We haven’t exhausted the traditional ways of making slices.

make with size=n capacity=n

To use this pattern, we can invoke make([]T, n) and then use an index over each slice’s item. Is it performant? How much better?

diff --git a/makestyles/bench_test.go b/makestyles/bench_test.go
index 7239d97..3058f3e 100644
--- a/makestyles/bench_test.go
+++ b/makestyles/bench_test.go
@@ -3,6 +3,7 @@ package makestyles
 import "testing"
 
 func makeIt(n int) (data []int) {
+	data = make([]int, n)
 	for i := 0; i < n; i++ {
 		data[i] = i
 	}

Results

After Bencher runs, we can look at our Github checks:

which when clicked on demonstrates the results:

and then if we navigate to the Bencher details URL which looks like:

Shoot-out

If we compare the results side by side, we can see the following:

Verdict

In this instance make([]<T>, size) is more performant than make([]<T>, 0, size) which is more performant than just a plain append in almost every single dimension that matters.

References

Resource URL
append Go standard library https://golang.org/pkg/builtin#make
Bencher website https://bencher.orijtech.com/
Bencher on the Github marketplace https://github.com/marketplace/gobencher/