1.1 原问题
cap = 6
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// 添加一个空切片
s = append(s, 0)
printSlice(s)
// 这个切片会按需增长
s = append(s, 1)
printSlice(s)
// 可以一次性添加多个元素
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
上述程序运行结果
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4] //该行中的 cap=6 无法理解
经过百度大部分水文后,查出来的误人子弟的 Go 语言 1.18 版本中切片的扩容机制如下:【!!!经过后面分析可知,该机制不完整,请读者注意!!!】
一、 如果切片的容量小于1024个元素,那么扩容的时候切片的 cap 就乘以2;一旦元素个数超过1024个元素,增长因子就变成 1.25,即每次增加原来容量的四分之一。
二、如果扩容之后,还没有超过原底层数组的容量,那么切片中的指针指向的位置就还是原数组;如果扩容之后,超过了原底层数组的容量,那么 Go 就会开辟一块新的内存,先把原来元素的值拷贝过来,再进行添加新元素操作,这种情况丝毫不会影响到原数组。
cap
1.2 新问题
22413312
package main
import "fmt"
func main() {
t := make([]int, 100)
printSlice(t)
t = append(t, 666)
printSlice(t)
fmt.Println("--------------------")
r := make([]int, 10000)
printSlice(r)
r = append(r, 888)
printSlice(r)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
}
上述代码运行结果
len=100 cap=100
len=101 cap=224
--------------------
len=10000 cap=10000
len=10001 cap=13312
二、源码剖析
2.1 growslice()
%GOROOT%/src/runtime/slice.go
// 位于 %GOROOT%/src/runtime/slice.go 文件中
func growslice(et *_type, old slice, cap int) slice {
//省略部分判断代码
//计算扩容部分
//其中 cap : 所需容量(比如上述例子中 2 + 3 = 5),newcap : 最终申请的容量(最终打印的结果 6)
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
}
}
}
//省略部分判断代码
}
old.len < 1024cap > doublecapnewcap = cap = 5
et.sizeint
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)
}
capmemnewcapuintptr
// uintptr is an integer type that is large enough to hold the bit pattern of any pointer
// uintptr 是一个整数类型,不是指针类型,但是足够保存任何一种指针
type uintptr uintptr
sys.PtrSize
// %GOROOT%/src/internal/goarch/goarch.go 文件中有 ptrSize 的定义和计算方式
// PtrSize is the size of a pointer in bytes - unsafe.Sizeof(uintptr(0)) but as an ideal constant.
// It is also the size of the machine's native word size (that is, 4 on 32-bit systems, 8 on 64-bit).
const PtrSize = 4 << (^uintptr(0) >> 63)
2.2 roundupsize()
capmemroundupsize()%GOROOT%/src/runtime/msize.go
// 位于 %GOROOT%/src/runtime/msize.go 文件中
// Returns size of the memory block that mallocgc will allocate if you ask for the size.
func roundupsize(size uintptr) uintptr {
if size < _MaxSmallSize {
if size <= smallSizeMax-8 {
return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
} else {
return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
}
}
if size+_PageSize < size {
return size
}
return round(size, _PageSize)
}
_MaxSmallSize32 << 1032768_MaxSmallSize = 32768%GOROOT/src/runtime/sizeclasses.goMaxSmallSize = 32 << 10%GOROOT/src/runtime/mksizeclasses.go
roundupsize()_MaxSmallSize (32768)smallSizeDivsmallSizeMaxlargeSizeDivsize_to_classclass_to_size
2.3 原问题 (1.1) 的实际扩容流程
intet.sizesys.ptrSizegrowslice()switchcase et.size == sys.PtrSizecapmem = roundupsize(uintptr(newcap) * sys.PtrSize)return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])capmem = 48newcap = 48 / 8 = 6
//在 %GOROOT%/src/runtime/sizeclassed.go 文件中定义
const (
_MaxSmallSize = 32768
smallSizeDiv = 8
smallSizeMax = 1024
largeSizeDiv = 128
_NumSizeClasses = 68
_PageShift = 13
)
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
var size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv + 1]uint8{32, 33, 34, 35, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 41, 42, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 48, 48, 48, 49, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 54, 54,
......
65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67}
三、结论总结
3.1 新问题 (1.2) 的实际扩容流程
3.1.1 向长度和容量均为 100 的切片中添加 1 个元素,最终的长度是 101,但是容量为什么是 224?
intet.sizesys.ptrSizegrowslice()switchcase et.size == sys.PtrSizecapmem = roundupsize(uintptr(newcap) * sys.PtrSize)return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])capmem = 1792newcap = 1792 / 8 = 224
3.1.2 向长度和容量均为 10000 的切片中添加 1 个元素,最终的长度是 10001,但是容量为什么是 13312?
intet.sizesys.ptrSizegrowslice()switchcase et.size == sys.PtrSizecapmem = roundupsize(uintptr(newcap) * sys.PtrSize)return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])newcap = 106496 / 8 = 13312
3.2 便于理解的扩容流程
intet.sizesys.ptrSizegrowslice()switchcase et.size == sys.PtrSizecapmem = roundupsize(uintptr(newcap) * sys.PtrSize)roundupsize()%GOROOT%/src/runtime/sizeclasses.gobytes/span(8,16](48,64]roundupsize()newcap = 48 / 8 = 6
3.1.1 和 3.1.2 中的问题也可以借助这个方法理解,下面我再叙述一下 3.1.1 的理解过程,3.1.2 就请各位读者自行推导了,有问题可以在评论区留言~。
intet.sizesys.ptrSizegrowslice()switchcase et.size == sys.PtrSizecapmem = roundupsize(uintptr(newcap) * sys.PtrSize)roundupsize()(1536,1792]newcap = 1792 / 8 = 224
// 位于 %GOROOT%/src/runtime/sizeclasses.go 文件中
// class bytes/obj bytes/span objects tail waste max waste min align
// 1 8 8192 1024 0 87.50% 8
// 2 16 8192 512 0 43.75% 16
// 3 24 8192 341 8 29.24% 8
// 4 32 8192 256 0 21.88% 32
// 5 48 8192 170 32 31.52% 16
// 6 64 8192 128 0 23.44% 64
// 7 80 8192 102 32 19.07% 16
// 8 96 8192 85 32 15.95% 32
// 9 112 8192 73 16 13.56% 16
// 10 128 8192 64 0 11.72% 128
// 11 144 8192 56 128 11.82% 16
// 12 160 8192 51 32 9.73% 32
// 13 176 8192 46 96 9.59% 16
// 14 192 8192 42 128 9.25% 64
// 15 208 8192 39 80 8.12% 16
// 16 224 8192 36 128 8.15% 32
// 17 240 8192 34 32 6.62% 16
// 18 256 8192 32 0 5.86% 256
// 19 288 8192 28 128 12.16% 32
// 20 320 8192 25 192 11.80% 64
// 21 352 8192 23 96 9.88% 32
// 22 384 8192 21 128 9.51% 128
// 23 416 8192 19 288 10.71% 32
// 24 448 8192 18 128 8.37% 64
// 25 480 8192 17 32 6.82% 32
// 26 512 8192 16 0 6.05% 512
// 27 576 8192 14 128 12.33% 64
// 28 640 8192 12 512 15.48% 128
// 29 704 8192 11 448 13.93% 64
// 30 768 8192 10 512 13.94% 256
// 31 896 8192 9 128 15.52% 128
// 32 1024 8192 8 0 12.40% 1024
// 33 1152 8192 7 128 12.41% 128
// 34 1280 8192 6 512 15.55% 256
// 35 1408 16384 11 896 14.00% 128
// 36 1536 8192 5 512 14.00% 512
// 37 1792 16384 9 256 15.57% 256
// 38 2048 8192 4 0 12.45% 2048
// 39 2304 16384 7 256 12.46% 256
// 40 2688 8192 3 128 15.59% 128
// 41 3072 24576 8 0 12.47% 1024
// 42 3200 16384 5 384 6.22% 128
// 43 3456 24576 7 384 8.83% 128
// 44 4096 8192 2 0 15.60% 4096
// 45 4864 24576 5 256 16.65% 256
// 46 5376 16384 3 256 10.92% 256
// 47 6144 24576 4 0 12.48% 2048
// 48 6528 32768 5 128 6.23% 128
// 49 6784 40960 6 256 4.36% 128
// 50 6912 49152 7 768 3.37% 256
// 51 8192 8192 1 0 15.61% 8192
// 52 9472 57344 6 512 14.28% 256
// 53 9728 49152 5 512 3.64% 512
// 54 10240 40960 4 0 4.99% 2048
// 55 10880 32768 3 128 6.24% 128
// 56 12288 24576 2 0 11.45% 4096
// 57 13568 40960 3 256 9.99% 256
// 58 14336 57344 4 0 5.35% 2048
// 59 16384 16384 1 0 12.49% 8192
// 60 18432 73728 4 0 11.11% 2048
// 61 19072 57344 3 128 3.57% 128
// 62 20480 40960 2 0 6.87% 4096
// 63 21760 65536 3 256 6.25% 256
// 64 24576 24576 1 0 11.45% 8192
// 65 27264 81920 3 128 10.00% 128
// 66 28672 57344 2 0 4.91% 4096
// 67 32768 32768 1 0 12.50% 8192
四、参考文章