Make your Go code efficient using make() when creating slices
Introduction
Slice is one of the powerful data types in Go which allows combining related values of the same type. In this blog, we will explore how we can improve the performance of your Go code faster using make() function when creating slices.
For better comparison, we initially create a slice using the var keyword.
Using var keyword:
In the below example, we create a very simple slice:
package main
func main() {
usingVar()
}
func usingVar() []string {
var sports []string
sports = append(sports, "Volleyball")
sports = append(sports, "Swimming")
sports = append(sports, "Running")
sports = append(sports, "Badminton")
sports = append(sports, "Baseball")
sports = append(sports, "Hockey")
return sports
}
Here, a sports slice is created with an empty array, with length and capacity being zero and value being nil.
var sports []string // len - 0, cap - 0, value=nil
As you may know, during append Go runtime will create a new array if there is no space in the underlying array. Since there is no space in our sports slice, the below steps are performed by Go runtime when executing this code,
When we append "Volleyball", the underlying array length is zero and hence Go runtime will create a new array with a length and capacity equal to one.
When we append "Swimming", again there is no space in the array. Due to this, the below steps are executed:
Create a new array with a length and capacity of two
Go runtime will copy the value "Volleyball" from the existing array to the newly created array
Append "Swimming" to a newly created array
Delete the old array
The same procedure is followed for the remaining appends as well.
You can imagine the total time and computing resources spent when there are millions of records.
By the way, the same story is also valid for slices which are initialized using := syntax like below,
sports := []string{}
Using make() function:
make() would be a better choice when you approximately know the size of the elements you are going to store. make() function takes the below parameters to create a slice,
data type,
length,
capacity - Optional. When not provided, length is considered for capacity.
Here is the extension of the above code using make().
package main
func main() {
usingVar()
usingMake()
}
func usingVar() []string {
var sports []string
sports = append(sports, "Volleyball")
sports = append(sports, "Swimming")
sports = append(sports, "Running")
sports = append(sports, "Badminton")
sports = append(sports, "Baseball")
sports = append(sports, "Hockey")
return sports
}
func usingMake() []string {
sports := make([]string, 0, 6)
sports = append(sports, "Volleyball")
sports = append(sports, "Swimming")
sports = append(sports, "Running")
sports = append(sports, "Badminton")
sports = append(sports, "Baseball")
sports = append(sports, "Hockey")
return sports
}
usingMake(), we ask to create a slice using make() with zero as the length and capacity to six for the underlying array. This means the underlying array can store up to six elements.
sports := make([]string, 0, 6)
So in this example, there is no need to create new arrays as we have only six elements and the current array is capable of storing these six elements. Hence, we save time and computation power.
Even if we have to append an additional value(7th element) to the sports slice, Go runtime will create a new array with double the capacity of the underlying array - in this example, capacity would be increased to 12. Still, it's better to use make() as the new array is created only when the size grows beyond the consciously provided capacity.
So, use make() when creating slices to improve the performance of your code.
How to measure the performance?
We can use the "benchmark" tool in Go to measure the performance for usingVar() and usingMake() functions to know the efficiency gain.
package main
import (
"testing"
)
func BenchmarkWithMake(b *testing.B) {
for i := 0; i < b.N; i++ {
usingMake()
}
}
func BenchmarkWithVar(b *testing.B) {
for i := 0; i < b.N; i++ {
usingVar()
}
}
With the above code, we have dedicated for loop to test the performance of usingMake() and usingVar() functions. During benchmark execution, b.N used in for loop will be adjusted at runtime until we get a reliable result.
Below command has to be executed to trigger the performance measurement.
go test -bench .
Here are the results:
BenchmarkWithMake-10 18519922 57.97 ns/op
BenchmarkWithVar-10 7669695 156.8 ns/op
PASS
ok GoBlogs 2.767s
BenchmarkWithMake():
executed on 10 cores - represented by BenchmarkUsingMake-10
loop ran for 18519922 times - value for b.N, determined at runtime
took 57.97 ns per iteration on average.
BenchmarkWithVar():
executed on 10 cores - represented by BenchmarkWithVar-10
loop ran for 7669695 times - value for b.N, determined at runtime
took 156.8 ns per iteration on average.
This blog explains how to improve code performance by using the make() function to create slices instead of using var keyword or := syntax.
Benchmarking with the Go tool showed that make() was 2.7 times faster than var, and is recommended for efficient code.
Subscribe to my newsletter
Read articles from Sampathkumar Subramaniam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sampathkumar Subramaniam
Sampathkumar Subramaniam
Technical delivery lead with expertise in CI/CD pipelines and its workload migration to Microsoft Azure DevOps. Passionate in team building, mentoring, technical program management, DevOps with expertise in Go programming. Certification:- Google Cloud Certified Cloud Digital Leader Google Project Management Professional Google IT Automation using Python