深入理解 Go slice 扩容机制
创始人
2025-05-30 09:02:23
0

前言

go 的切片可以自动地进行扩容,当调用append方法时,如果len == cap,即容纳不下新的元素的时, 底层会帮我们为切片的底层数组分配更大的内存空间,然后把旧的切片的底层数组指针指向新的内存中

  • 注意不是根据底层数据的容量来判断是否扩容,而是根据slice的属性cap,cap可能比数组实际容量小

分配更大的内存时预留的一定的buffer

  • 如果不预留,只考虑当前新增的元素,则每次append时都会发生数据拷贝,成本太高

于是slice扩容的重点就在于,预留多少buffer?

1.18以前

直接看源码:

源码位置:/src/runtime/slice.go growslice

func growslice(et *_type, old slice, cap int) slice {// ...newcap := old.capdoublecap := newcap + newcapif 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}if newcap <= 0 {newcap = cap}}}// ...capmem = roundupsize(uintptr(newcap))

新容量的计算规则如下:

  • 需要的容量比2倍老容量大:使用需要的容量

    • 一般发生于append一个较大的slice时,例如 append(s, s1...)
  • 如果老容量小于1024,按照2倍扩容

  • 如果老容量大于等于1024,按照1.25倍扩容,对应源码newcap += newcap / 4

  • 如果newcap溢出了int最大值,不扩容

最后再按照内存分配块的大小,向上修正

内存分配各个size如下:{0, 8, 16, 24, 32, 48, 64, 80, 96, 112...}

例如,如果slice为int类型,有5个元素,应该占40个字节,但由于分配到size=48的内存块,会向上修正到48字节,也就是cap变为6

func main() {s := []int{}s = append(s, []int{1, 2, 3, 4, 5}...)fmt.Println(cap(s))  // 结果为6
}

这个扩容规则有什么问题?

  1. 不够平滑:容量小于1024时2倍扩容,大于1024突然降到1.25倍扩容

  2. 容量增长不单调,正常应该是较大的初始容量扩容后有较大的最终容量

    1. 例如以下代码:
x1 := make([]int, 897)
x2 := make([]int, 1024)
y := make([]int, 100)
println(cap(append(x1, y...))) // 2048
println(cap(append(x2, y...))) // 1280

x1的容量比x2小,都增加100个元素后x1的容量反而比x2大

1.18以后

go在这次提交中 runtime: make slice growth formula a bit smoother修改了扩容规则:

其用一个更平滑的公式来计算扩容因子,在256个元素之前还是2倍扩容因子,在256个元素后逐步减少,不同容量下的扩容因子如下:

starting capgrowth factor
2562.0
5121.63
10241.44
20481.35
40961.30

改版后代码如下:

// ...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {newcap = cap
} else {const threshold = 256if old.cap < threshold {newcap = doublecap} else {for 0 < newcap && newcap < cap {newcap += (newcap + 3*threshold) / 4}if newcap <= 0 {newcap = cap}}
}
// ...

在容量256以后,预留的buffer容量为 1/4原容量,加上了 3/4 * 256

这样当old.cap为256时还是2倍扩容,随着容量增大3/4 * 256占原容量的比例逐渐降低最终收敛于0,达到平滑扩容的效果

总结

  • 1.18以前:

    • 容量小于1024时为2倍扩容,大于等于1024时为1.25倍扩容
  • 1.18以后:

    • 小于256时为2倍扩容,大于等于256时的扩容因子逐渐从2减低为1.25
  • 不管1.18前后,最终需要按照内存分配块向上修正

相关内容

热门资讯

python多线程 文章目录一、简介1.1 多线程的特性1.2 GIL二、线程1.2 单线程1.3 多线程三、线程池3....
綦江区成为家校共育区域性研究实... 4月26日—27日,中国教育学会“十三五”教育科研重点课题“基础教育阶段家校共育的理论与实践研究”现...
曲阜《教子有方》&lt... 一个孩子的教育成功,是全家人的成功!一个孩子的教育失败,是全家人的失败!——————赵国彦任何事业的...
《家庭教育》杂志创刊三十年贺词... 中国家庭教育学会副会长、北京师范大学教授赵忠心(杭州《家庭教育》杂志最新或2023(历届)第一二期)...
美国男人为什么不包二奶? 外国... 1、信仰美国的社会,大部分人们对宗教很虔诚。而对于教徒对婚姻、家庭的忠诚,宗教有很明确的要求,有些宗...
DirectX12(D3D12... 目录1、前言1.1、一些感慨1.2、运行效果展示1.3、示例简介1.4、示例操作说明1.5、本章内容...
《扬帆优配》机构关注目标锁定,... 证券时报·数据宝统计,3月13日至17日,A股市场58家组织算计进行66...
家庭教育三个关键词——陪伴、阅... 通过什么方式来重视家庭教育?家庭教育中父母也需要通过阅读来成长读什么比阅读更重要。现在每年中国出版量...
最新或2023(历届)朔州市小... 朔州市凡年满6周岁(8月31日以前满6岁,年龄截止日期以当年8月31日为准,2009年8月31日前出...
最新或2023(历届)晋城市小... 016年晋城高平市小学一年级新生招生报名工作即将启动,晋城高平市的很多家长关心最新或2023(历届)...
最运动 最博物 最辽阔 最人文... 终于,我们的第一个夏令营来了。这三年来,总有朋友在追问,博雅有没有夏令营,我们想送孩子来。我们也越来...
哈佛专家:最毁孩子的9个家庭教... 哈佛大学心理学硕士张璐将做客“青榄公开课”,为广大家长和孩子免费讲授一堂公开课《读懂孩子的小秘密》。...
基于Java web的员工管理... 摘 要 本文以员工工资管理系统的实际应用需要出发,搭建基于MVC开发的员工工资管理系统...
最新或2023(历届)最新临沂... 1临沂市第一实验小学(临沂一小)2临沂市第二实验小学(临沂二小)3临沂市童星实验学校4临沂市红旗路实...
最新或2023(历届)最新日照... 1日照市实验小学  2日照市五莲县实验学校(小学部)  3日照市五莲县实验小学  4山东省五莲县实验...
最新或2023(历届)晋中市小... 最新或2023(历届)晋中榆次区小学一年级新生招生报名工作即将启动,晋中榆次区的很多家长关心最新或2...
最新或2023(历届)最新莱芜... 双峰联小电话:0634-6832300邮编:271104地址:莱芜市钢城区艾山街道办事处胡家宅村北卞...
最新或2023(历届)最新德州... 1、实验小学(含西区)省级规范化学校2、天衢东路小学, 省级规范化学校3、湖滨北路小学, 省级规范化...
【ConfluxNews】20... 【ConfluxNews】2023.3.20 ---------------------------...
java实现“数据平滑升级” 文章目录一、摘要二、前提场景说明:三、项目用到的脚本和代码1.项目目录长这样2.jav...