以前是从别人的博客看到关于 slice 的扩容机制,有人问起来就搬别人博客上看到的内容回答,回答的时候总是不够自信,今天通过学习源码和借助 dlv 工具,算是明白了 slice 扩容的事情,这里记录下自己总结的结论,slice 源码也不难,有想深入了解的伙伴可以看 src/runtime/slice.go 中的 growslice 方法。
要是有理解错误的地方,希望能大佬指正。
我源码阅读的是 Go 1.14.6 版本
这里以 old = append(old, append...) 为例子说明
1 触发扩容的条件: 如果 old.len + append.len > old.cap 就会触发扩容
2: 扩容的时候调用的 src/runtime.growslice growslice(et *_type, old slice, cap int) slice
方法,该方法接收 slice 的元素类型(el)、old slice 以及预期容量长度 cap, 预期 cap: expectCap = old.len + append.len
3: growslice 的方法会预先获取到新的容量;再根据 slice 元素类型(size) 做内存对齐,最终计算出新的 cap 值
3.1: 预先获取新 newCap 的方式如下: 如果 expectCap > 2 * old.cap, 则 newCap = expectCap ; 否则,如果 old.cap < 1024, newCap = 2 * old.cap, 否则以 old.cap 的长度作为 newCap 初始值,以的 0.25 的步长循环增长,直到 newCap >= expectCap
3.2: 内存对齐,根据 newCap * slice 元素 size 获取内存空间,根据 golang 内置的一个内存表向上去整,拿到做内存对其后的内存空间 capmem,然后用 capmem 除以 slice 元素类型 size 得到最终的 newCap
看到网上很多人写的 slice 的扩容机制,其实只是写到了 3.1 这步 (也可能是因为和我看的 Go 版本不同),我做实验验证也发现有时候出现和文章描述不符合的结果,今天看了源码才终于明白,出现不符合的情况就是因为有 3.2 的情况出现
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.