引言

最近在刷面试题的过程中,因为本地Go使用的是1.20版本,而网上关于 Go slice扩容策略的描述还大多停留在 2021年前的版本,也就是Go1.17版本和之前的所有版本,遂分享出来。

如果你使用的Go版本大于等于Go1.18,可要千万注意啦。

Go1.18之前:

Go1.18后是256Go1.18后由表达式计算

Go1.18开始:

小于256改成了根据增长因子(growth factor)扩容

so,如果面试被问到,回答了这个细节,应该会加分吧?

什么时候会触发扩容

append()growslice()

扩容源码在哪里

主要看Go源码的2个文件(Go1.17版本):

  • src/cmd/compile/internal/ssagen/ssa.go
func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value{
 // ...
}
  • src/runtime/slice.go
func growslice(et *_type, old slice, cap int) slice {
 // ...
}
growslice()append()

源码对比

之前的版本
// go.17 src/runtime/slice.go
func growslice(et *_type, old slice, cap int) slice {
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {// cap=len+3,只有当切片长度<=2时走这个逻辑,故可忽略
        newcap = cap
    } else {
        // 1. 先计算新的容量大小
        if old.cap < 1024 {
            newcap = doublecap
        } else {
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
    // … 内存对齐计算、数据拷贝等逻辑
}

Go1.18和后续版本(最新的Go1.20保持一致):

// go1.18 src/runtime/slice.go
func growslice(et *_type, old slice, cap int) slice {
    // ...
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        const threshold = 256
        if old.cap < threshold {
            newcap = doublecap
        } else {
            for 0 < newcap && newcap < cap {
                // Transition from growing 2x for small slices
                // to growing 1.25x for large slices. This formula
                // gives a smooth-ish transition between the two.
                newcap += (newcap + 3*threshold) / 4
            }
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
    //...后续代码(内存对齐等)没有变动
}

增长因子(growth factor)示例

所谓的增长因子其实就是一个计算公式:

newcap += (newcap + 3*threshold) / 4

如果看不懂,Keith Randall 大神在2021年9月的提交中给出了一个增长因子的示例(runtime: make slice growth formula a bit smoother):

runtime: make slice growth formula a bit smoother

    Instead of growing 2x for < 1024 elements and 1.25x for >= 1024 elements,
    use a somewhat smoother formula for the growth factor. Start reducing
    the growth factor after 256 elements, but slowly.

    starting cap    growth factor
    256             2.0
    512             1.63
    1024            1.44
    2048            1.35
    4096            1.30

通过这个示例,我们只要明白大切片(超过256)扩容时,不再是固定 1.25 倍,而是平滑下降即可。

newcap

总结

扩容策略:

Go1.18后是256Go1.18后由表达式计算,不再是固定值

作者简介:一线Gopher,公众号《Go和分布式IM》运营者,开源项目: CoffeeChat 、interview-golang 发起人 & 核心开发者,终身程序员。