go slice 扩容深度分析(1.18版本的golang已经改变之前的分配方式)
1、切片扩容常规操作
如果超过容量,会重新扩一个内存,创建新的数组,并把指针指向这个数组,容量如果不超过1024会扩一倍,如果容量超过1024扩容量会每次增加25%。
package main
import (
"fmt"
)
func main() {
a := []int{}
for i := 0; i < 33; i++ {
a = append(a, i)
fmt.Print(cap(a), " ")
}
}
结果:
1 2 4 4 8 8 8 8 16 16 16 16 16 16 16 16 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 64
2、切片扩容非常规操作(一次插入多个数字)
package main
import "fmt"
func main() {
b := []int{23, 51}
b = append(b, 4, 5, 6)
fmt.Println("cap of b is ",cap(b))
}
猜猜结果是多少?
cap of b is 6
想知道结果我们翻看golang 的源码,我的版本是1.18beta1,老版本的扩容代码和新版本有些许区别
代码位置:src/runtime/slice.go
//老版本
newcap := old.cap
if newcap+newcap < cap {
newcap = cap
} else {
for {
if old.len < 1024 {
newcap += newcap
} else {
newcap += newcap / 4
}
if newcap >= cap {
break
}
}
}
// 1.18beta版
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
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
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
- 第一行的 old.cap:扩容前的容量,对于此例,就是 2
- 第二行的 cap:扩容前容量加上扩容的元素数量,对于此例,就是 2+3
新版本的和老版本的区别在于新版本把阈值设为256,并且改变了增长公式
newcap += (newcap + 3*threshold) / 4,使之增长更加平滑
看代码那最终容量不是5吗?
其实不然,newcap是计算出扩容后的预估容量,并不是最终的容量,要计算最终的容量,还需要参考另一个维度,也就是内存分配。
关于内存管理模块的代码,在 runtime/sizeclasses.go
// class bytes/obj bytes/span objects tail waste max waste
// 1 8 8192 1024 0 87.50%
// 2 16 8192 512 0 43.75%
// 3 32 8192 256 0 46.88%
// 4 48 8192 170 32 31.52%
...
// 17 256 8192 32 0 5.86%
// 18 288 8192 28 128 12.16%
// 19 320 8192 25 192 11.80%
// 20 352 8192 23 96 9.88%
// 21 384 8192 21 128 9.51%
// 22 416 8192 19 288 10.71%
// 23 448 8192 18 128 8.37%
// 24 480 8192 17 32 6.82%
// 25 512 8192 16 0 6.05%
...
// 66 32768 32768 1 0 12.50%
5个int是40字节,向上取整是48字节所以cap是6的原因。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!