Go切片底层实现

121 阅读2分钟

Slice的底层实现原理

在Go语言中,Slice的底层实现主要包括以下三个部分:

  1. 指向底层数组的指针
  2. Slice的长度
  3. Slice的容量

其中,指向底层数组的指针指向的是数组的第一个元素,Slice的长度指的是Slice中元素的个数,而Slice的容量指的是Slice从第一个元素开始到底层数组的最后一个元素的个数。Slice的底层实现使用了动态数组的概念,这意味着Slice的长度可以动态地增加或减少。

Go 1.18版本切片扩容

Go1.18不再以1024为临界点,而是设定了一个值为256的threshold,以256为临界点;超过256,不再是每次扩容1/4,而是每次增加(旧容量+3*256)/4;

  • 当新切片需要的容量cap大于两倍扩容的容量,则直接按照新切片需要的容量扩容;

  • 当原 slice 容量 < threshold 的时候,新 slice 容量变成原来的 2 倍;

  • 当原 slice 容量 > threshold,进入一个循环,每次容量增加(旧容量+3*threshold)/4。

image.png

在1.18中,优化了切片扩容的策略,让底层数组大小的增长更加平滑: 通过减小阈值并固定增加一个常数,使得优化后的扩容的系数在阈值前后不再会出现从2到1.25的突变,该commit作者给出了几种原始容量下对应的“扩容系数”:

image.png

总结

总的来说,Go的设计者不断优化切片扩容的机制,其目的只有一个:就是控制让小的切片容量增长速度快一点,减少内存分配次数,而让大切片容量增长率小一点,更好地节省内存。

  • 如果只选择翻倍的扩容策略,那么对于较大的切片来说,现有的方法可以更好的节省内存。

  • 如果只选择每次系数为1.25的扩容策略,那么对于较小的切片来说扩容会很低效。

  • 之所以选择一个小于2的系数,在扩容时被释放的内存块会在下一次扩容时更容易被重新利用