Golang Which way is the best to declare array/slice

eye-catch Golang

Have you ever considered the best way to declare an array/slice in Golang? Golang offers several ways to declare it. It’s a good thing always to declare it in the same way to keep consistency in the project. Let’s check them in this post and determine which one to use in your project.

Sponsored links

What is the different between a Slicen and an Array

An Array is different from a Slice. The difference is whether the size is fixed or not. This is the explanation on the official page.

An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array.

https://go.dev/tour/moretypes/7
Sponsored links

How to declare a Slice

There are 3 ways to declare a Slice.

slice1 := []int{}
// nil == false; len:cap = 0:0
fmt.Printf("nil == %t; len:cap = %d:%d\n", slice1 == nil, len(slice1), cap(slice1))

var slice2 []int
// nil == true; len:cap = 0:0
fmt.Printf("nil == %t; len:cap = %d:%d\n", slice2 == nil, len(slice2), cap(slice2))

slice3 := make([]int, 0)
// nil == false; len:cap = 0:0
fmt.Printf("nil == %t; len:cap = %d:%d\n", slice3 == nil, len(slice3), cap(slice3))

As you can see above, only the middle one is nil. The other two ways allocate memory and the variables point to the memory to a slice with 0 length. We’ll check the performance later though, the third one looks the slowest because it calls make function to create a zero-length slice.

How to declare an Array

There are basically two ways to declare an array but let’s try 3 ways.

array1 := [100]int{}
// len:cap = 100:100
fmt.Printf("len:cap = %d:%d\n", len(array1), cap(array1))

array2 := make([]int, 0, 100)
// len:cap = 0:100
fmt.Printf("len:cap = %d:%d\n", len(array2), cap(array2))

array3 := make([]int, 100, 100)
// len:cap = 100:100
fmt.Printf("len:cap = %d:%d\n", len(array3), cap(array3))

All of them allocate memory. The first and third ways initialize all the elements with 0, while the second way allocates the memory for 100 elements but assigns 0 length to the variable. The second one looks the fastest one for the declaration.

Which way is the fastest? Performance comparison

According to wiki page in GitHub of Golang, using var is preferred.

Let’s test the performance.

Declaration speed

Firstly, we’ll check the declaration speed only.

func BenchmarkArrayEmptyDeclaration(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = []int{}
    }
}

func BenchmarkArrayVarDeclaration(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var _ []int
    }
}

func BenchmarkArrayMakeDeclaration(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = make([]int, 0)
    }
}

func BenchmarkArraySizeDeclaration(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = [100]int{}
    }
}

func BenchmarkArrayMakeSizeDeclaration(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = make([]int, 0, 100)
    }
}

func BenchmarkArrayMakeSizeDeclaration2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = make([]int, 100, 100)
    }
}

The result is this.

$ go test ./benchmark -benchmem -bench Array
goos: linux
goarch: amd64
pkg: play-with-go-lang/benchmark
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkArrayEmptyDeclaration-4                        1000000000               0.4344 ns/op          0 B/op          0 allocs/op
BenchmarkArrayVarDeclaration-4                          1000000000               0.4176 ns/op          0 B/op          0 allocs/op
BenchmarkArrayMakeDeclaration-4                         1000000000               0.4641 ns/op          0 B/op          0 allocs/op
BenchmarkArraySizeDeclaration-4                         1000000000               0.6343 ns/op          0 B/op          0 allocs/op
BenchmarkArrayMakeSizeDeclaration-4                     1000000000               0.4808 ns/op          0 B/op          0 allocs/op
BenchmarkArrayMakeSizeDeclaration2-4                    1000000000               0.4440 ns/op          0 B/op          0 allocs/op

I executed it 3 times.

_ = []int{}               // 0.4344  0.3681  0.3676
var _ []int               // 0.4176  0.3870  0.3752
_ = make([]int, 0)        // 0.4641  0.3651  0.3650
_ = [100]int{}            // 0.6343  0.3738  0.3729
_ = make([]int, 0, 100)   // 0.4808  0.3724  0.3697
_ = make([]int, 100, 100) // 0.4440  0.3637  0.3658

The Go prefered way is the fastest for the first execution but actually, it doesn’t have any difference. It depends on the CPU usage situation at the execution time.

Data assignment speed

It is more important to know the assignment speed because it doesn’t make sense to declare an array/slice without data.

As you know, there are two ways to assign a value.

array = append(array, newData)
array[i] = newData

Since append function needs to allocate memory, it is probably slower than using an index. Let’s check the difference here.

func BenchmarkArrayVarDeclarationAndAssign(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var array []int
        for j := 0; j < 100; j++ {
            array = append(array, j)
        }
    }
}

func BenchmarkArrayMakeDeclarationAndAssign(b *testing.B) {
    for i := 0; i < b.N; i++ {
        array := make([]int, 0)
        for j := 0; j < 100; j++ {
            array = append(array, j)
        }
    }
}

func BenchmarkArraySizeDeclarationAndAssign(b *testing.B) {
    for i := 0; i < b.N; i++ {
        array := [100]int{}
        for j := 0; j < 100; j++ {
            array[j] = j
        }
    }
}

func BenchmarkArrayMakeSizeDeclarationAndAssign(b *testing.B) {
    for i := 0; i < b.N; i++ {
        array := make([]int, 0, 100)
        for j := 0; j < 100; j++ {
            array = append(array, j)
        }
    }
}
func BenchmarkArrayMakeSizeDeclarationAndAssign2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        array := make([]int, 100, 100)
        for j := 0; j < 100; j++ {
            array[j] = j
        }
    }
}

The result is this.

$ go test ./benchmark -benchmem -bench Array
goos: linux
goarch: amd64
pkg: play-with-go-lang/benchmark
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkArrayEmptyDeclarationAndAssign-4                1359242               822.4 ns/op          2040 B/op          8 allocs/op
BenchmarkArrayVarDeclarationAndAssign-4                  1452523               848.5 ns/op          2040 B/op          8 allocs/op
BenchmarkArrayMakeDeclarationAndAssign-4                 1309866               903.0 ns/op          2040 B/op          8 allocs/op
BenchmarkArraySizeDeclarationAndAssign-4                21182074                51.84 ns/op            0 B/op          0 allocs/op
BenchmarkArrayMakeSizeDeclarationAndAssign-4            11617975                92.20 ns/op            0 B/op          0 allocs/op
BenchmarkArrayMakeSizeDeclarationAndAssign2-4           23169355                50.86 ns/op            0 B/op          0 allocs/op

I executed it 3 times.

// ---- using append ----
array := []int{}               // 822.4  788.2  1360
var array []int                // 848.5  873.3  1165
array := make([]int, 0)        // 903.0  774.4  954.5
array := make([]int, 0, 100)   // 92.20  89.45  95.24

// ----- using index ----
array := [100]int{}            // 51.84  47.08  50.43
array := make([]int, 100, 100) // 50.86  46.41  54.00

It’s a big difference. It’s important to allocate the necessary memory in advance. The memory allocation is done only once if the size is specified. However, append function allocates memory multiple times because it doesn’t know the max size.

It’s important to specify the size at the declaration time. If additional speed is required, it’s better to use an index to assign a new value. append function needs to look for the end position of the array and thus it’s a bit slower.

Overview

Declare slice with var keyword if the size is unknown.

var slice int[]

Specify the length and the capacity (size) if it is known.

array := make([]int, 100, 100)

If you look for the fastest way over code consistency, assign a value by index.

array := make([]int, 100, 100)
for i := 0; i < 100; i++ {
    array[i] = newValue
}

Comments

Copied title and URL