从源码解读切片容量增加的计算步骤

shanrengo · · 513 次点击 · · 开始浏览    

本例子基于go1.17.x版本

package main

import "fmt"

func main(){
  s := []int{1,2}
  oldCap := cap(s)
  s = append(s, 3,4,5)
  newCap := cap(s)
  fmt.Printf("oldCap = %d, newCap = %d",oldCap,newCap)
}

根据例子,我们看到s一开始cap是2,当添加元素3的时候,容量变成4;添加元素4的时候,容量不变;添加5的时候,容量cap是不是应该是8呢?这里很多朋友以为应该都是8呀,不是说slice容量小于1024的时候,新slice的容量就变成原来的2倍吗?为何这里是=6?

我们打开slice的源码看看:

源码目录src/runtime/slice.go

我们找到这个方法:

func growslice(et *_type, old slice, cap int) slice d

当我们添加元素5的时候,一个临时的cap=5,作为第三个参数传入growslice的里面

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
   newcap = cap
}

newcap = oldcap = 2

doublecap = 4

cap =5 > doublecap

=》newcap = 5

接着源码我们看到,进入了

capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
4 << (^uintptr(0) >> 63)   sys.PrtSize  32系统是4,64位系统就是8

我电脑是64位的,所以这里capmen = 5 * 8 = 40 

继续传入roudupsize方法中:

func roundupsize(size uintptr) uintptr {
   if size < _MaxSmallSize {
      if size <= smallSizeMax-8 {
         return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
      } else {
         return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
      }
   }
   if size+_PageSize < size {
      return size
   }
   return alignUp(size, _PageSize)
}

通过源码,我们看到:

const (
   _MaxSmallSize   = 32768
   smallSizeDiv    = 8
   smallSizeMax    = 1024
   largeSizeDiv    = 128
   _NumSizeClasses = 68
   _PageShift      = 13
)

这里我们的40 < _MaxSmallSize,且<smallSizeMax 所以得到

return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])

 

func divRoundUp(n, a uintptr) uintptr {
   // a is generally a power of two. This will get inlined and
   // the compiler will optimize the division.
   return (n + a - 1) / a
}

divRoundUp就是一个取余的运算,(40 + 8 - 1)/8 = 5

size_to_class8是啥?我们继续看源码,看到它是系统定义的一个[smallSizeMax/smallSizeDiv + 1]uint8数组

var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}

 我们拿取余的5作为索引,在数组中取值就是5,

继续在class_to_size中取值:

var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}

 

继续用5当做索引,取值就是48

最后计算

newcap = int(capmem / sys.PtrSize)

sys.PtrSize = 8,这里就是48/8 = 6

 

今天就分享到这里


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077

513 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传