Golang 1.18版本之后的slice扩容策略

问题导出

最近学习Golang,对slice的扩容策略很感兴趣。
首先看下面的一段代码

var arr1, arr2, arr3, arr4 []int64
var arr5, arr6, arr7, arr8 []int32

for i := 0; i < 25; i++ {
	arr1 = append(arr1, int64(i))
	arr5 = append(arr5, int32(i))
}

arr3 = append(arr3, arr1...)
arr7 = append(arr7, arr5...)

for i := 0; i < 513; i++ {
	arr2 = append(arr2, int64(i))
	arr6 = append(arr6, int32(i))
}

arr4 = append(arr4, arr2...)
arr8 = append(arr8, arr6...)

fmt.Printf("arr1 cap=%d\t", cap(arr1))
fmt.Printf("arr2 cap=%d\t", cap(arr2))
fmt.Printf("arr3 cap=%d\t", cap(arr3))
fmt.Printf("arr4 cap=%d\n", cap(arr4))
fmt.Printf("arr5 cap=%d\t", cap(arr5))
fmt.Printf("arr6 cap=%d\t", cap(arr6))
fmt.Printf("arr7 cap=%d\t", cap(arr7))
fmt.Printf("arr8 cap=%d\n", cap(arr8))

上面的这段程序运行的结果如下所示(Golang版本为1.21.1)

arr1 cap=32		arr2 cap=848	arr3 cap=26		arr4 cap=608
arr5 cap=32		arr6 cap=864	arr7 cap=28		arr8 cap=576

对于arr1和arr5的结果看上去应该很好理解,在插入第17个元素的时候,发生扩容,容量变为2倍,似乎就是这样。但是,对于arr2和arr6的结果就一脸疑惑,还有就是对于arr3、arr4、arr7和arr8的结果也无从下手分析。为什么会这样?通过对比arr2和arr6的容量,似乎扩容之后的容量还与单个元素所占内存相关,是这样吗?接下来我们一起来探究。

sizeclasses.go文件

在Golang中,有一个sizeclasses.go,存在runtime包下,该文件的部分代码如下所示:

// 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

为什么要说这个文件,因为后面slice扩容策略所需要的对比文件。需要关注bytes/obj列

单个元素插入导致的扩容策略

容量小于256

对应上述例子中的arr1、arr5,计算slice的容量大小的公式如下所示:

newSlice = oldSlice * 2 * 单个元素的所占内存  -> 查看sizeclasses中对应的表格,得到离这个数字最近的大于它的数
newSlice = 得到之后的数 / 单个元素的所占内存

上述的方法得到的就是最终slice扩容后的容量大小,需要注意的是,我们所查的表是bytes/obj列
为了验证上述方法是否正确,我们将例子中的arr5进行手动的计算:
最开始的arr1的容量为0,插入第一个元素的时候,发生了扩容,按照计算公式
newSlice = 0 * 2 * 4 得到0,通过查看表格,最近的是8,然后newSlice = 8 / 4得到2
这个就是为什么int32的slice插入第一个元素的时候容量是2,这个时候又有一个有趣的事情

var a []int32
a2 := []int32{1}
a = append(a, int32(3))
fmt.Println(cap(a), cap(a2))

上述的代码得到的结果是

2  1

其实这个是非常容易理解的,一个是发生了扩容,一个是初始化。具体就不展开。
回到对arr5的扩容分析。插入第一个元素之后,目前的容量变为了2,当第三个元素插入的时候 ,再次发生扩容,通过计算2 * 2 * 4 = 16,查表正好有,那么最后的结果为16 / 4 = 4,所以插入第三个元素的时候,容量扩容为4。同样的道理,在插入第5个元素的时候,发生扩容,4 * 2 * 4 = 32,查看表格也有,那么得到32 / 4 = 8。通过同样的方法可以得到arr5最后的容量为什么是32。
对于arr1的分析和arr5是一样的步骤,只不过是单个元素的内存为8字节

容量大于256

容量大于256,对应例子中的arr2和arr6。计算扩容后的方法如下所示:

newSlice = ((old + (oldSize + 3*256)/4) * 单个元素所占字节数) ->查看表格获得最近的大于它的值
获得的值 / 单个元素所占字节数  就是最终扩容的大小

上述的方法就是展示容量大于256之后的计算方法
同样的,我们以arr2为例子,分析其扩容的结果。
首先在arr2插入第257个元素的时候,发生扩容:(256 + (256 + 3 * 256) / 4) * 8 = 4096,通过查看表,得到离它最近的大于它的值正好是4096,然后4096 / 8 = 512。接着,在插入第513个元素的时候,再次发生扩容,带入公式:(512+ (512+ 3 * 256) / 4) * 8 = 6656,通过查表可以得到,符合要求的值为67846784 / 8 = 848。这个就是arr2在大于256之后的扩容计算流程。arr6也可以通过同样的方法计算得到其最终的结果,只不过int32的单个内存为4字节。

上述就是所有单个元素插入slice,发生扩容时候的容量计算方法。

批量插入元素导致扩容

批量插入元素导致扩容,对应例子中的arr3, arr4, arr7和arr8。
此时介绍slice.go文件中slice扩容的关键代码。

newcap := oldCap
doublecap := newcap + newcap
if newLen > doublecap {
	newcap = newLen
} else {
	const threshold = 256
	if oldCap < threshold {
		newcap = doublecap
	} else {
		// Check 0 < newcap to detect overflow
		// and prevent an infinite loop.
		for 0 < newcap && newcap < newLen {
			// 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 = newLen
		}
	}
}

上述的代码解决了单个插入扩容和批量插入扩容的问题,需要注意的是其中得到的newCap数值并不是真正的cap结果。需要将newCap * 单个元素的字节数,然后还是通过查表得到最后的结果。

在这一节中,不分析最开始的例子中的计算流程,而是看下面的一段代码:

var arr9 []int32
for i := 0; i < 32; i++ {
	arr9 = append(arr9, int32(i))
}
fmt.Println("arr9 cap=", cap(arr9))

arr9 = append(arr9, arr6...)
fmt.Println("arr9 cap=", cap(arr9), " len=", len(arr9))
arr9 = append(arr9, arr6...)
fmt.Println("arr9 cap=", cap(arr9), " len=", len(arr9))

上面代码中的arr6是开头例子中的arr6。其运行结果如下所示

arr9 cap= 32
arr9 cap= 576  len= 545
arr9 cap= 1344  len= 1058

在这个例子中是在有原数据的情况下插入大量的数据导致扩容。分析一下输出的结果。
首先,第一个输出很简单,和最开始的例子一样。对于第二个输出,批量插入了513个元素,此时发生了扩容,回到slice.go的源文件中的扩容部分代码,很明显 newLen > doublecap,所以newCap就是32 + 513 = 545,然后545 * 4 = 2180,通过查表,得到2304,然后2304 / 4 = 576。对于第三个输出,newLen = 1058, newLen < doublecap,然后就进入那个循环中,576 + (576 + 3 * 256) / 4 = 912 。因为912 < 1058, 再次循环 912 + (912+ 3 * 256) / 4 = 1332, 此时1322 > 1058,那么newCap就是1322。最后1322 * 4 = 5288,通过查表,得到53765376 / 4 = 1344

总结

想要理解slice扩容, slice.go文件和sizeclasses.go文件是最为重要的。通过结合slice.go和sizeclasses.go文件可以熟练掌握slice扩容的本质。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值