问题
This is a pretty straightforward question:
If the capacity of a slice in Golang can be exceeded, why is there a capacity parameter in the first place?
I reckon this has to do with the memory management, some kind of "knowing where to allocate the slice in memory" but I don't know exactly.
回答1:
If the capacity of a slice in Golang can be exceeded, why is there a capacity parameter in the first place?
I don't know what you mean by this, but the capacity can't be exceeded. Slices can be indexed up to its length (exclusive) which can't exceed the capacity, and they can be re-sliced up to its capacity (inclusive).
Spec: Index expressions:
A primary expression of the form
a[x]
If
a
is not a map: ...the indexx
is in range if0 <= x < len(a)
, otherwise it is out of range
And Spec: Slice expressions:
...the primary expression:
a[low : high]
For arrays or strings, the indices are in range if
0 <= low <= high <= len(a)
, otherwise they are out of range. For slices, the upper index bound is the slice capacitycap(a)
rather than the length.
And also:
...the primary expression:
a[low : high : max]
The indices are in range if
0 <= low <= high <= max <= cap(a)
, otherwise they are out of range.
You may provide the capacity to the builtin make() thinking of future growth, so fewer allocations will be needed should you need to append elements to it or should you need to reslice it. The builtin append() just reslices the slice you append to if it has enough capacity for the additional elements, but it has to allocate a new backing array (and copy the existing contents into it) if it has no room for the new elements. append()
will return the new slice which may or may not point to the original backing array.
Let's see an example. Let's create a slice with 0 length and capacity, and append 10 elements to it. And to see when a new reallocation happens, we also print the address of its first element (the 0th element):
fmt.Println("With 0 capacity")
s := make([]int, 0)
for i := 0; i < 10; i++ {
s = append(s, i)
fmt.Println(i, &s[0])
}
This outputs:
With 0 capacity
0 0x416030
1 0x416030
2 0x416040
3 0x416040
4 0x452000
5 0x452000
6 0x452000
7 0x452000
8 0x434080
9 0x434080
As you can see, a new backing array is allocated when we appended the third (i=2
), fifth (i=4
) and ninth elements (i=8
), and also when we appended the first element (as the original backing array could not hold any elements).
Now let's repeat the above example when we create the initial slice with again length = 0 but with capacity = 10:
fmt.Println("With 10 capacity")
s = make([]int, 0, 10)
for i := 0; i < 10; i++ {
s = append(s, i)
fmt.Println(i, &s[0])
}
Now output will be:
With 10 capacity
0 0x44c030
1 0x44c030
2 0x44c030
3 0x44c030
4 0x44c030
5 0x44c030
6 0x44c030
7 0x44c030
8 0x44c030
9 0x44c030
As you can see, the address of the first element never changed, which means there were no new backing array allocations happening in the background.
Try the example on the Go Playground.
回答2:
To increase the capacity of a slice one must create a new, larger slice and copy the contents of the original slice into it.
If the capacity of a slice in Golang can be exceeded, why is there a capacity parameter in the first place?
Suppose we know ahead of time the capacity of slice, then we can use capacity parameter of make built in function to allocate memory instead of increasing capacity of slice dynamically using append which is not that memory efficient.
so in below example
type Element struct {
Number int
}
func main() {
Max := 100000
startTime := time.Now()
// Capacity given
elements1 := make([]Element, Max, Max)
for i := 0; i < Max; i++ {
elements1[i].Number = i
}
elapsedTime := time.Since(startTime)
fmt.Println("Total Time Taken with Capacity in first place: ", elapsedTime)
startTime = time.Now()
// Capacity not given
elements2 := make([]Element, 0)
for i := 0; i < Max; i++ {
elements2 = append(elements2, Element{Number: i})
}
elapsedTime = time.Since(startTime)
fmt.Println("Total Time Taken without capacity: ", elapsedTime)
}
output
Total Time Taken with Capacity in first place: 121.084µs
Total Time Taken without capacity: 2.720059ms
Time Taken to build slice with capacity at first place is less that to build dynamically
So to answer you question, capacity parameter is in the first place for better performance and memory efficiency
回答3:
There are many excellent posts in the golang blog. This one on slices, will give you a detailed insight into the underlying implementation of slices and how capacity works.
The post goes through how the append
operation works and how the capacity value lets append
know whether to reuse the underlying memory array, or allocate a bigger array when more capacity is needed.
来源:https://stackoverflow.com/questions/54782079/why-exactly-is-there-a-capacity-parameter-when-creating-a-slice-in-golang