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.
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
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