golang中slice扩容一定是double或1.25倍吗
本文是对 Go slice扩容分析之 不是double或1.25那么简单的学习与记录
问题
依据大多数资料,slice的扩容机制是当切片的容量小于1024时,进行双倍扩容;当大于1024时,进行1.25倍扩容
,见如下代码:
1 | var sli = []int{} |
输出为:
1 | 1 |
又见如下代码:
1 | var sli2 = []int{} |
输出为:
1 | [0 1 2 3 4 5 6 7 8 9] |
用更”准确”的话描述,是当cap<1024时,cap的值一定是2的n次方,且cap>=len;每当发生append使len增加,如果导致len>cap,此时cap会先于append操作进行double
即有
1 | var sli3 = []int{} |
结果为:
1 | 512 |
对于
1 | var sli4 = []int{} |
结果为:
1 | 1024 |
1024*
1.25 = 1280
看似无懈可击的结果, 不过, 果真确凿如此吗?
上面的操作是每次append一个元素,考虑另一种情形,一次性append很多元素,会发生什么呢?
当同时append进多个元素时,如下:
1 | package main |
结果为:
1 | len of old a is 2 |
匪夷所思?
其实是因为内存对齐
简化以上代码:
1 | package main |
输出为:
1 | len of old m is 2 |
为什么一次性append多个,最后切片的长度为6; 而多次append单个元素,最后切片的长度为8?
切片扩容的源码,是/src/runtime/slice.go中的growslice
方法:
1 |
|
原理
使用gdb调试工具进行调试
强烈建议先阅读此文,学习GDB基本用法
使用l 10
可查看第10行附近的源码
使用 b 12
在第12行处设置断点 (可以设置多个断点)
这样会在运行到第12行时停止,可查看变量的值、堆栈情况等;
使用info b
查看断点处情况
可使用 s
, 跳入断点,并看执行情况
可使用 r
运行代码
可使用 p 变量名
,显示变量值
如果调试过程中出现value optimized out,说明编译器进行了内联优化。
可通过
go build -gcflags "-N -l" -o 自定义的二进制文件名称 原始Go文件.go
命令,禁用编译器优化
gdb调试 出现value optimized out解决方法
可使用n
单步运行. 非常好用,可以看到调用链
使用c
, 使程序继续往下运行,直到再次遇到断点或程序结束
使用q
, 退出gdb
调用roundupsize()函数,进行(向上)内存对齐
roundupsize()位于go/src/runtime/msize.go,更具体介绍与使用可参考
1 | // Copyright 2009 The Go Authors. All rights reserved. |
go/src/runtime/sizeclasses.go
1 | // Code generated by mksizeclasses.go; DO NOT EDIT. |
> 当创建一个对象时,需要分配一块内存。假设创建的对象需要52 byte,系统是不会真就给分配52byte大小的内存,首先会根据上面代码中第6行注释部分,来计算需要分配的内存大小。
可参考[go/src/runtime/msize.go] 中的 roundupsize 函数。也就是需要向上取整,即 48<52<64,所以申请52byte,实际会分配64byte大小的内存。
另外还需要了解: 如果每次创建一个对象,Go runtime都向计算机中申请一块内存,而在程序运行时是会频繁的创建对象的,这样效率将会大大降低。 所以程序会预先申请好一些内存块,其大小就是 8、16、32、48 等等,这样在程序申请内存时序就可以把申请好的内存选一块给我们,也就提高了效率
growslice的三个参数: 第一个是类型(上例中为int64),第二个是扩容前切片a(元素为2和3),第三个参数是预估容量5(原有切片容量加上新加元素个数,即2+3=5)
对容量进行计算:
1 | ... |
又因为int64 类型大小为8字节,故而下面会走到et.size == sys.PtrSize:代码块
对于
const PtrSize = 4 << (^uintptr(0) >> 63)
:1 | package main |
执行结果为:
1 | uintptr(0)值为: 0 |
1 | case et.size == sys.PtrSize: |
关于maxAlloc:
1 | package main |
输出为:
1 | _64bit值为: 1 |
更多参考:
深度解密Go语言之Slice 搜关键字 roundupsize
Linux内核中有rounddown方法,取数的最高二进制阶数
原文作者: fliter
原文链接:
http://www.dashen.tech/2019/06/28/golang中slice扩容一定是double或1-25倍吗/版权声明: 转载请注明出处