What is the point in setting a slice's capacity?
在Golang中,我们可以使用内置的make()函数创建具有给定初始长度和容量的切片。
考虑以下几行,切片的长度设置为1,其容量设置为3:
1 2 3 4 5 6
| func main() {
var slice = make([]int, 1, 3)
slice[0] = 1
slice = append(slice, 6, 0, 2, 4, 3, 1)
fmt.Println(slice)
} |
我很惊讶地看到这个程序打印了:
[1 6 0 2 4 3 1]
号
这让我想知道——如果append()可以简单地吹过它,那么最初定义切片容量的意义是什么?设置足够大的容量是否有性能提升?
切片实际上只是管理底层数组的一种奇特方法。它自动跟踪大小,并根据需要重新分配新空间。
当您附加到一个切片时,它的容量在每次超过当前容量时都会加倍。它必须复制所有元素才能做到这一点。如果您知道在开始之前它将有多大,那么您可以通过预先获取它来避免一些复制操作和内存分配。
当你用一个片来提供容量时,你设置初始容量,而不是任何限制。
有关Slices的一些有趣的内部细节,请参阅Slices上的此博客文章。
- 在某一点上,它停止加倍,并开始以25%的增量增加。我认为这发生在1024个元素之后。
- 这不仅仅是需要时间的复制操作,它也是alloc。
一个slice是一个简单array的奇妙抽象。你有各种各样的好特性,但在它的核心深处,有一个array。(出于某种原因,我以相反的顺序解释以下内容)。因此,如果/当您指定3的capacity时,在内存中分配一个长度为3的数组,您可以在不需要重新分配内存的情况下将该数组分配到append。该属性在make命令中是可选的,但请注意,无论您是否选择指定,slice都将始终具有capacity。如果您指定了一个length(它也一直存在),那么slice可以索引到该长度。其余的capacity隐藏在幕后,因此在使用append时,它不必分配一个全新的阵列。
下面是一个更好地解释力学的例子。
埃多克斯1〔15〕
基础array与int的零值(即0的零值)的3一起分配:
埃多克斯1〔20〕
但是,length被设置为1,因此切片本身只打印[0],如果尝试索引第二或第三个值,它将panic,因为slice的机制不允许这样做。如果你对它使用s = append(s, 1),你会发现它实际上是被创建来包含zero的值,直到length,你最终会得到[0,1]。此时,您可以在整个基础array填充之前再次执行append,另一个append将强制它分配一个新的,并以双倍的容量复制所有值。这实际上是一个相当昂贵的操作。
因此,对您的问题的简短回答是,可以使用预分配capacity来大幅提高代码的效率。尤其是如果slice要么最终会非常大,要么包含复杂的structs(或两者兼有),因为struct的zero值实际上是其fields中每一个的zero值。这并不是因为它可以避免分配这些值,因为它无论如何都必须分配这些值,而是因为每次需要调整基础数组的大小时,append都必须重新分配新array中充满这些零值的值。
短操场示例:https://play.golang.org/p/lgayvlw-jr