前言
最近在跟小伙伴讨论 Golang 切片的扩容机制,好一些文章的结论都是错误的。在这里,我也带着好奇心去翻阅了源码。那么我就把我理解的 Golang 扩容机制梳理一下。
当前文章讨论的 go 版本:1.17.5
先给结论
-
如果当前所需容量 (cap) 大于原先容量的两倍 (doublecap),申请容量(newcap)为当前所需容量(cap),即是 newcap = cap。接着,再在此的基础上,匹配内存规格,向上扩大为可以覆盖当前容量的内存规格。详见底下的源码展示
-
如果当前所需容量(cap)小于原来容量的两倍(doublecap),则:
-
如果原切片长度(old.len)小于1024,则最终申请容量(newcap)等于原容量的两倍(doublecap);
-
否则,以 1.25 的速度增长容量,即是 newcap += newcap / 4 ;
举个例子
常规的递增的例子,我就跳过了,我们来看看特殊的例子,如下:
func main() {
fmt.Println("main")
ints := make([]int, 2)
fmt.Printf("len:%d cap:%d", len(ints), cap(ints))
fmt.Println()
ints = append(ints, 2, 3, 4, 3, 4, 3, 4)
fmt.Printf("len:%d cap:%d", len(ints), cap(ints))
}
结果:
len:2 cap:2
len:9 cap:10
为什么 cap 不是 9,而是变成了 10 呢?
源码位置
位置:
go1.17.5\src\runtime\slice.go
我们注意到,前半部分的代码还是比较容易懂的。
就是判断 cap 是不是大于两倍的 old.cap ,如果大于的话,就将 cap 赋值给 newcap
接着,再来讨论是否大于 1024 的情况。
在分配为好容量之后,还会有一步:匹配内存规格(规格表见文末),就拿我上面据的例子来分析。
例子中,申请的是 int 切片,int 占用 8 个字节,那么 9 x 8 = 72
72 向上匹配内存规格为:80(查看文末规格表可知)
则需要申请的容量为:80/8 = 10
这部分的工作,通常是 roundupsize() 函数完成的。
func growslice(et *_type, old slice, cap int) slice {
...省略一部分代码
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 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
}
}
}
var overflow bool
var lenmem, newlenmem, capmem uintptr
// Specialize for common values of et.size.
// For 1 we don't need any division/multiplication.
// For sys.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
// For powers of 2, use a variable shift.
switch {
case et.size == 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
case et.size == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
case isPowerOfTwo(et.size):
var shift uintptr
if sys.PtrSize == 8 {
// Mask shift for better code generation.
shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
} else {
shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
}
lenmem = uintptr(old.len) << shift
newlenmem = uintptr(cap) << shift
capmem = roundupsize(uintptr(newcap) << shift)
overflow = uintptr(newcap) > (maxAlloc >> shift)
newcap = int(capmem >> shift)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
capmem = roundupsize(capmem)
newcap = int(capmem / et.size)
}
...省略一部分代码
}
//from runtime.go\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%
// 5 64 8192 128 0 23.44%
// 6 80 8192 102 32 19.07%
// 7 96 8192 85 32 15.95%
// 8 112 8192 73 16 13.56%
// 9 128 8192 64 0 11.72%
// 10 144 8192 56 128 11.82%
// 11 160 8192 51 32 9.73%
// 12 176 8192 46 96 9.59%
// 13 192 8192 42 128 9.25%
// 14 208 8192 39 80 8.12%
// 15 224 8192 36 128 8.15%
// 16 240 8192 34 32 6.62%
// 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%
// 26 576 8192 14 128 12.33%
// 27 640 8192 12 512 15.48%
// 28 704 8192 11 448 13.93%
// 29 768 8192 10 512 13.94%
// 30 896 8192 9 128 15.52%
// 31 1024 8192 8 0 12.40%
// 32 1152 8192 7 128 12.41%
// 33 1280 8192 6 512 15.55%
// 34 1408 16384 11 896 14.00%
// 35 1536 8192 5 512 14.00%
// 36 1792 16384 9 256 15.57%
// 37 2048 8192 4 0 12.45%
// 38 2304 16384 7 256 12.46%
// 39 2688 8192 3 128 15.59%
// 40 3072 24576 8 0 12.47%
// 41 3200 16384 5 384 6.22%
// 42 3456 24576 7 384 8.83%
// 43 4096 8192 2 0 15.60%
// 44 4864 24576 5 256 16.65%
// 45 5376 16384 3 256 10.92%
// 46 6144 24576 4 0 12.48%
// 47 6528 32768 5 128 6.23%
// 48 6784 40960 6 256 4.36%
// 49 6912 49152 7 768 3.37%
// 50 8192 8192 1 0 15.61%
// 51 9472 57344 6 512 14.28%
// 52 9728 49152 5 512 3.64%
// 53 10240 40960 4 0 4.99%
// 54 10880 32768 3 128 6.24%
// 55 12288 24576 2 0 11.45%
// 56 13568 40960 3 256 9.99%
// 57 14336 57344 4 0 5.35%
// 58 16384 16384 1 0 12.49%
// 59 18432 73728 4 0 11.11%
// 60 19072 57344 3 128 3.57%
// 61 20480 40960 2 0 6.87%
// 62 21760 65536 3 256 6.25%
// 63 24576 24576 1 0 11.45%
// 64 27264 81920 3 128 10.00%
// 65 28672 57344 2 0 4.91%
// 66 32768 32768 1 0 12.50%
阅读文档:
https://www.cnblogs.com/zpcoding/p/13259943.html