很多 Go 的文章都提到 append 扩容的具体实现,为了确认文章中提到的 1024 阈值,我翻了下 Go 1.18 的源码,发现实现切片扩容的 growslice 函数实际已经变化了,源代码的位置也有一定的改变,因此在此记录一下。实际上 Go 的源代码变化的非常快,函数的位置也经常改变,我这里记录的源码也只是Go 1.18 这个特定版本的实现和代码位置。
同时在 Go 1.18 Release Notes 中提到:
The built-in function
append
now uses a slightly different formula when deciding how much to grow a slice when it must allocate a new underlying array. The new formula is less prone to sudden transitions in allocation behavior.
以下只摘出核心代码:
append 的中间代码生成部分挪到了 src/cmd/compile/internal/ssagen/ssa.go
核心的 gorwslice 函数在 src/runtime/slice.go
1func growslice(et *_type, old slice, cap int) slice {
2 //...
3 newcap := old.cap
4 doublecap := newcap + newcap
5 if cap > doublecap {
6 newcap = cap
7 } else {
8 const threshold = 256
9 if old.cap < threshold {
10 newcap = doublecap
11 } else {
12 // Check 0 < newcap to detect overflow
13 // and prevent an infinite loop.
14 for 0 < newcap && newcap < cap {
15 // Transition from growing 2x for small slices
16 // to growing 1.25x for large slices. This formula
17 // gives a smooth-ish transition between the two.
18 newcap += (newcap + 3*threshold) / 4
19 }
20 // Set newcap to the requested cap when
21 // the newcap calculation overflowed.
22 if newcap <= 0 {
23 newcap = cap
24 }
25 }
26 }
27 //...
28}
可以看出在 Go 1.18 中,扩容策略(不考虑内存对齐的情况下)变成了:
- 如果期望容量大于当前容量的两倍就会使用期望容量
- 如果当前切片的长度小于 256 就会将容量翻倍
- 如果当前切片的长度大于 256 就会每次增加 25% 的同时再增加 192(256的3/4),直到新容量大于期望容量
按照注释的说法是新的公式 newcap += (newcap + 3*threshold) / 4 会使得容量的变化更加平滑。顺带一提,在之前的版本中公式为 newcap += newcap / 4
源码中的实现才是最准确的,网上的文章都有时效性,切勿奉为圭臬。