0x00 引子
先来看一段代码:
a := make([]int, 0, 2)
a = append(a, 1, 2)
fmt.Printf("a:%v, len:%d, cap:%d\n", a, len(a), cap(a)) // ①
a = append(a, 3, 4, 5)
fmt.Printf("a:%v, len:%d, cap:%d\n", a, len(a), cap(a)) // ②
// 输出结果为:
// ① a:[1 2], len:2, cap:2
// ② a:[1 2 3 4 5], len:5, cap:6
为什么在②处的切片a的cap变成了6?
0x01 Go切片扩容机制
Step.1 预估扩容后的容量 newCap1
变量定义
oldLen
:扩容前的 lennewLen
:append 操作后切片内的元素数oldCap
: 扩容前的 capnewCap1
:预估 扩容后的 capnewCap2
:实际 扩容后的 cap
预估规则
- 若
newLen > 2 * oldCap
,则newCap1 = newLen
- 若
newLen ≤ 2 * oldCap
- 若
oldLen < 1024
,则newCap1 = oldCap * 2
- 若
oldLen ≥ 1024
,则newCap1 = oldCap * 1.25
- 若
Step.2 结合内存分配计算 newCap2
两个核心:
- 预估容量 newCap1 * 元素类型大小 = 内存分配需求
- 内存分配器根据 内存分配需求 和 spanClass 规格,按块分配
- 实际分配容量 newCap2 = 满足内存分配需求 的最小 spanClass大小 / 元素类型大小
其中,目前版本 spanClass 规格大致有:
8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192 …
e.g.
已知:
- newCap1 = 3
- 元素类型大小 = 8
则:
- 内存分配需求 = 24 (newCap1 * 元素类型大小)
- 结合 spanClass大小 知,此时应分配的内存大小为 32
- 则 newCaps2 = 4 (满足内存分配需求 的最小 spanClass大小 32 / 元素类型大小 8)
0x02 引子问题分析
a := make([]int, 0, 2)
a = append(a, 1, 2)
fmt.Printf("a:%v, len:%d, cap:%d\n", a, len(a), cap(a)) // ①
a = append(a, 3, 4, 5)
fmt.Printf("a:%v, len:%d, cap:%d\n", a, len(a), cap(a)) // ②
// 输出结果为:
// ① a:[1 2], len:2, cap:2
// ② a:[1 2 3 4 5], len:5, cap:6
Step.1 预估扩容后的容量 newCap1
命中 「 newLen > 2 * oldCap
,则 newCap1 = newLen
」 规则 ,newCap1 = 5
Step.2 结合内存分配计算 newCap2
已知:
- newCap1 = 5
- 元素类型大小 = 8
则:
- 内存分配需求 = 40 (newCap1 * 元素类型大小)
- 结合 spanClass大小 知,此时应分配的内存大小为 48
- 则 newCaps2 = 6 (满足内存分配需求 的最小 spanClass大小 48 / 元素类型大小 8)