0

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

 1 year ago
source link: https://studygolang.com/articles/35633
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

shanrengo · 大约2小时之前 · 87 次点击 · 预计阅读时间 3 分钟 · 大约8小时之前 开始浏览    

本例子基于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)
}

e769558f3e6fc56feff902dd3d3c225c.png

根据例子,我们看到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

今天就分享到这里


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

280

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK