go slice 扩容分析

以此为例进行讲解

1
2
3
s := []int64{1, 2}
ns := append(s, []int64{1, 2, 3}...)
fmt.Println(cap(ns))

cap 扩容最小元素数
newcap 扩容后实际元素数

第一步确定新的空间大小newcap

如果cap大于2*oldcap,那么newcap等于cap
否则
如果cap小于1024,newcap等于2*oldcap
否则newcap以1.25*newcap循环增大直至newcap>=cap

在本例中cap为5,oldcap为2,cap大于2*oldcap,所以newcap等于5

代码如下,位置在$GOROOT/src/runtime/slice.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func growslice(et *_type, old slice, cap int) slice {
// 省略一些判断...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
// 省略一些后续...
}

第二步根据类型计算所需字节数

5*8字节=40字节

第三步利用roundupsize方法进行内存向上对齐

对齐大小与类型有关,具体见runtime/sizeclasses.go,其中bytes/obj的这一列是go中预定义的对象大小,最小是8b,最大是32K,
比如int64类型40字节为例,由于并没有size为40的类型,于是40向上取整,取到48,也就是6个元素(取整逻辑在roundupsize函数中)
最终ns的cap大小为6

参考资料

Go slice扩容深度分析