In Golang, we can use the builtin make()
function to create a slice with a given initial length and capacity.
Consider the following lines, the slice's length is set to 1, and its capacity 3:
func main() {
var slice = make([]int, 1, 3)
slice[0] = 1
slice = append(slice, 6, 0, 2, 4, 3, 1)
fmt.Println(slice)
}
I was surprised to see that this program prints:
[1 6 0 2 4 3 1]
This got me wondering- what is the point of initially defining a slice's capacity if append()
can simply blow past it? Are there performance gains for setting a sufficiently large capacity?
A slice is really just a fancy way to manage an underlying array. It automatically tracks size, and re-allocates new space as needed.
As you append to a slice, its capacity doubles in size every time it exceeds its current capacity. It has to copy all of the elements to do that. If you know how big it will be before you start, you can avoid a few copy operations and memory allocations by grabbing it all up front.
When you make
a slice providing capacity, you set tht initial capacity, not any kind of limit.
See this blog post on slices for some interesting internal details of slices.
A slice
is a wonderful abstraction of a simple array
. You get all sorts of nice features, but deep down at its core, lies an array
. (I explain the following in reverse order for a reason). Therefore, if/when you specify a capacity
of 3
, deep down, an array of length 3
is allocated in memory, which you can append
up to without having it need to reallocate memory. This attribute is optional in the make
command, but note that a slice
will always have a capacity
whether or not you choose to specify one. If you specify a length
(which always exists as well), the slice
be indexable up to that length. The rest of the capacity
is hidden away behind the scenes so it does not have to allocate an entirely new array when append
is used.
Here is an example to better explain the mechanics.
s := make([]int, 1, 3)
The underlying array
will be allocated with 3
of the zero value of int
(which is 0
):
[0,0,0]
However, the length
is set to 1
, so the slice itself will only print [0]
, and if you try to index the second or third value, it will panic
, as the slice
's mechanics do not allow it. If you s = append(s, 1)
to it, you will find that it has actually been created to contain zero
values up to the length
, and you will end up with [0,1]
. At this point, you can append
once more before the entire underlying array
is filled, and another append
will force it to allocate a new one and copy all the values over with a doubled capacity. This is actually a rather expensive operation.
Therefore the short answer to your question is that preallocating the capacity
can be used to vastly improve the efficiency of your code. Especially so if the slice
is either going to end up very large, or contains complex structs
(or both), as the zero
value of a struct
is effectively the zero
values of every single one of its fields
. This is not because it would avoid allocating those values, as it has to anyway, but because append
would have to reallocate new array
s full of these zero values each time it would need to resize the underlying array.
Short playground example: https://play.golang.org/p/LGAYVlw-jr