很多 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 中,扩容策略(不考虑内存对齐的情况下)变成了:

  1. 如果期望容量大于当前容量的两倍就会使用期望容量
  2. 如果当前切片的长度小于 256 就会将容量翻倍
  3. 如果当前切片的长度大于 256 就会每次增加 25% 的同时再增加 192(256的3/4),直到新容量大于期望容量

按照注释的说法是新的公式 newcap += (newcap + 3*threshold) / 4 会使得容量的变化更加平滑。顺带一提,在之前的版本中公式为 newcap += newcap / 4

源码中的实现才是最准确的,网上的文章都有时效性,切勿奉为圭臬。