领先的免费Web技术教程,涵盖HTML到ASP.NET

网站首页 > 知识剖析 正文

Slice 使?和扩容 扩容hynix tlc

nixiaole 2024-11-10 12:31:35 知识剖析 15 ℃

Slice 使?和扩容

Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?

go 版本:go1.21.2 darwin/amd64

Slice 的底层数组

Slice 是对数组的?个抽象,它指向底层数组。Slice 的底层数组是?个连续的内存空间,Slice 的元素类型都是相同的。Slice 的底层数组的地址是 Slice 的第?个元素的地址。

Slice 的结构

Slice 源码如下:

/src/runtime/slice.go

type slice struct { array unsafe.Pointer len int

cap int

}

Slice 有三个字段,分别是 array、len 和 cap。array 是?个指向底层数组的指针,len 表示 Slice 的?度,cap 表示 Slice 的容量。

Slice 的定义

Slice 的定义和数组类似,只是不需要?度。Slice 的定义格式如下:

var myslice []int

使?make 函数创建?个 Slice,格式如下:

myslice := make([]int, 10)

[]int 表示 Slice 的类型,10 表示 Slice 的?度,cap(myslice) 表示 Slice 的容量。

Slice 的扩容

Slice 的扩容是指 Slice 的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容

Slice 的容量。

使?append追加元素到 Slice 时

myslice = append(myslice, 1)

扩容的源码如下:

func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice { oldLen := newLen - num

if raceenabled {

callerpc := getcallerpc()

racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))

}

if msanenabled {

msanread(oldPtr, uintptr(oldLen*int(et.Size_)))

}

if asanenabled {

asanread(oldPtr, uintptr(oldLen*int(et.Size_)))

}

if newLen < 0 {

panic(errorString("growslice: len out of range"))

}

if et.Size_ == 0 {

// append should not create a slice with nil pointer but non-zero len.

// We assume that append doesn't need to preserve oldPtr in this case. return slice{unsafe.Pointer(&zerobase), newLen, newLen}

}

newcap := oldCap

doublecap := newcap + newcap if newLen > doublecap {

newcap = newLen

} else {

const threshold = 256 if oldCap < threshold {

newcap = doublecap

} else {

// Check 0 < newcap to detect overflow

// and prevent an infinite loop. for 0 < newcap && newcap < newLen {

// Transition from growing 2x for small slices

// to growing 1.25x for large slices. This formula

// gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) / 4

}

// Set newcap to the requested cap when

// the newcap calculation overflowed. if newcap <= 0 {

newcap = newLen

}

}

}

var overflow bool

var lenmem, newlenmem, capmem uintptr

// Specialize for common values of et.Size.

// For 1 we don't need any division/multiplication.

// For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.

// For powers of 2, use a variable shift. switch {

case et.Size_ == 1:

lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)

capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)

case et.Size_ == goarch.PtrSize:

lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize

capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)

case isPowerOfTwo(et.Size_): var shift uintptr

if goarch.PtrSize == 8 {

// Mask shift for better code generation.

shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63

} else {

shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31

}

lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift

capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)

capmem = uintptr(newcap) << shift default:

lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_

capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)

newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_

}

// The check of overflow in addition to capmem > maxAlloc is needed

// to prevent an overflow which can be used to trigger a segfault

// on 32bit architectures with this example program:

//

// type T [1<<27 + 1]int64

//

// var d T

// var s []T

//

// func main() {

// s = append(s, d, d, d, d)

// print(len(s), "\n")

// }

if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))

}

var p unsafe.Pointer if et.PtrBytes == 0 {

p = mallocgc(capmem, nil, false)

// The append() that calls growslice is going to overwrite from oldLen to newLen.

// Only clear the part that will not be overwritten.

// The reflect_growslice() that calls growslice will manually clear

// the region not cleared here. memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)

} else {

// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.

p = mallocgc(capmem, et, true)

if lenmem > 0 && writeBarrier.enabled {

// Only shade the pointers in oldPtr since we know the destination slice p

// only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)

}

}

memmove(p, oldPtr, lenmem)

return slice{p, newLen, newcap}

}

growslice 函数的参数 oldPtr 是 Slice 的底层数组指针,newLen 是 Slice 的新?度,oldCap 是 Slice 的旧容量,

num 是 Slice 的增??度,et 是 Slice 的元素类型。

扩容的逻辑如下:

  1. 如果 newLen ?于 0,抛出异常。代码如下:

if newLen < 0 {

panic(errorString("growslice: len out of range"))

}

  1. 如果 Slice 的元素类型的??为 0,返回?个新的 Slice。代码如下:

if et.Size_ == 0 {

return slice{unsafe.Pointer(&zerobase), newLen, newLen}

}

  1. 计算新的容量 newcap。代码如下:

newcap := oldCap

doublecap := newcap + newcap if newLen > doublecap {

newcap = newLen

} else {

const threshold = 256 if oldCap < threshold {

newcap = doublecap

} else {

for 0 < newcap && newcap < newLen { newcap += (newcap + 3*threshold) / 4

}

if newcap <= 0 { newcap = newLen

}

}

}

如果 Slice 的容量?于 1024,则新的 Slice 的容量是原来的 2 倍。如果 Slice 的容量?于等于 1024,则新的 Slice

的容量是原来的 1.25 倍,直到新的 Slice 的容量?于等于 newLen。

  1. 计算新的 Slice 的内存??。代码如下:

var overflow bool

var lenmem, newlenmem, capmem uintptr switch {

case et.Size_ == 1:

lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)

capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)

case et.Size_ == goarch.PtrSize:

lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize

capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)

case isPowerOfTwo(et.Size_): var shift uintptr

if goarch.PtrSize == 8 {

shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63

} else {

shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31

}

lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift

capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)

capmem = uintptr(newcap) << shift default:

lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_

capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)

newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_

}

  1. 如果 newcap ?于 maxAlloc,抛出异常。代码如下:

if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))

}

  1. 分配新的内存。代码如下:

var p unsafe.Pointer if et.PtrBytes == 0 {

p = mallocgc(capmem, nil, false) memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)

} else {

p = mallocgc(capmem, et, true)

if lenmem > 0 && writeBarrier.enabled { bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-

et.Size_+et.PtrBytes)

}

}

memmove(p, oldPtr, lenmem)

  1. 返回新的 Slice。# Slice 使?和扩容

Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?

go 版本:go1.21.2 darwin/amd64

Slice 的底层数组

Slice 是对数组的?个抽象,它指向底层数组。Slice 的底层数组是?个连续的内存空间,Slice 的元素类型都是相同的。Slice 的底层数组的地址是 Slice 的第?个元素的地址。

Slice 的结构

Slice 源码如下:

/src/runtime/slice.go

type slice struct { array unsafe.Pointer len int

cap int

}

Slice 有三个字段,分别是 array、len 和 cap。array 是?个指向底层数组的指针,len 表示 Slice 的?度,cap 表示 Slice 的容量。

Slice 的定义

Slice 的定义和数组类似,只是不需要?度。Slice 的定义格式如下:

var myslice []int

使?make 函数创建?个 Slice,格式如下:

myslice := make([]int, 10)

[]int 表示 Slice 的类型,10 表示 Slice 的?度,cap(myslice) 表示 Slice 的容量。

Slice 的扩容

Slice 的扩容是指 Slice 的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容

Slice 的容量。

使?append追加元素到 Slice 时

myslice = append(myslice, 1)

扩容的源码如下:

func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice { oldLen := newLen - num

if raceenabled {

callerpc := getcallerpc()

racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))

}

if msanenabled {

msanread(oldPtr, uintptr(oldLen*int(et.Size_)))

}

if asanenabled {

asanread(oldPtr, uintptr(oldLen*int(et.Size_)))

}

if newLen < 0 {

panic(errorString("growslice: len out of range"))

}

if et.Size_ == 0 {

// append should not create a slice with nil pointer but non-zero len.

// We assume that append doesn't need to preserve oldPtr in this case. return slice{unsafe.Pointer(&zerobase), newLen, newLen}

}

newcap := oldCap

doublecap := newcap + newcap if newLen > doublecap {

newcap = newLen

} else {

const threshold = 256 if oldCap < threshold {

newcap = doublecap

} else {

// Check 0 < newcap to detect overflow

// and prevent an infinite loop. for 0 < newcap && newcap < newLen {

// Transition from growing 2x for small slices

// to growing 1.25x for large slices. This formula

// gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) / 4

}

// Set newcap to the requested cap when

// the newcap calculation overflowed. if newcap <= 0 {

newcap = newLen

}

}

}

var overflow bool

var lenmem, newlenmem, capmem uintptr

// Specialize for common values of et.Size.

// For 1 we don't need any division/multiplication.

// For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.

// For powers of 2, use a variable shift. switch {

case et.Size_ == 1:

lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)

capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)

case et.Size_ == goarch.PtrSize:

lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize

capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)

case isPowerOfTwo(et.Size_): var shift uintptr

if goarch.PtrSize == 8 {

// Mask shift for better code generation.

shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63

} else {

shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31

}

lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift

capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)

capmem = uintptr(newcap) << shift default:

lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_

capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)

newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_

}

// The check of overflow in addition to capmem > maxAlloc is needed

// to prevent an overflow which can be used to trigger a segfault

// on 32bit architectures with this example program:

//

// type T [1<<27 + 1]int64

//

// var d T

// var s []T

//

// func main() {

// s = append(s, d, d, d, d)

// print(len(s), "\n")

// }

if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))

}

var p unsafe.Pointer if et.PtrBytes == 0 {

p = mallocgc(capmem, nil, false)

// The append() that calls growslice is going to overwrite from oldLen to newLen.

// Only clear the part that will not be overwritten.

// The reflect_growslice() that calls growslice will manually clear

// the region not cleared here. memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)

} else {

// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.

p = mallocgc(capmem, et, true)

if lenmem > 0 && writeBarrier.enabled {

// Only shade the pointers in oldPtr since we know the destination slice p

// only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)

}

}

memmove(p, oldPtr, lenmem)

return slice{p, newLen, newcap}

}

growslice 函数的参数 oldPtr 是 Slice 的底层数组指针,newLen 是 Slice 的新?度,oldCap 是 Slice 的旧容量,

num 是 Slice 的增??度,et 是 Slice 的元素类型。

扩容的逻辑如下:

  1. 如果 newLen ?于 0,抛出异常。代码如下:

if newLen < 0 {

panic(errorString("growslice: len out of range"))

}

  1. 如果 Slice 的元素类型的??为 0,返回?个新的 Slice。代码如下:

if et.Size_ == 0 {

return slice{unsafe.Pointer(&zerobase), newLen, newLen}

}

  1. 计算新的容量 newcap。代码如下:

newcap := oldCap

doublecap := newcap + newcap if newLen > doublecap {

newcap = newLen

} else {

const threshold = 256 if oldCap < threshold {

newcap = doublecap

} else {

for 0 < newcap && newcap < newLen { newcap += (newcap + 3*threshold) / 4

}

if newcap <= 0 { newcap = newLen

}

}

}

如果 Slice 的容量?于 1024,则新的 Slice 的容量是原来的 2 倍。如果 Slice 的容量?于等于 1024,则新的 Slice

的容量是原来的 1.25 倍,直到新的 Slice 的容量?于等于 newLen。

  1. 计算新的 Slice 的内存??。代码如下:

var overflow bool

var lenmem, newlenmem, capmem uintptr switch {

case et.Size_ == 1:

lenmem = uintptr(oldLen) newlenmem = uintptr(newLen)

capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem)

case et.Size_ == goarch.PtrSize:

lenmem = uintptr(oldLen) * goarch.PtrSize newlenmem = uintptr(newLen) * goarch.PtrSize

capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize)

case isPowerOfTwo(et.Size_): var shift uintptr

if goarch.PtrSize == 8 {

shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63

} else {

shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31

}

lenmem = uintptr(oldLen) << shift newlenmem = uintptr(newLen) << shift

capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift)

capmem = uintptr(newcap) << shift

default:

lenmem = uintptr(oldLen) * et.Size_ newlenmem = uintptr(newLen) * et.Size_

capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap)) capmem = roundupsize(capmem)

newcap = int(capmem / et.Size_) capmem = uintptr(newcap) * et.Size_

}

  1. 如果 newcap ?于 maxAlloc,抛出异常。代码如下:

if overflow || capmem > maxAlloc { panic(errorString("growslice: len out of range"))

}

  1. 分配新的内存。代码如下:

var p unsafe.Pointer if et.PtrBytes == 0 {

p = mallocgc(capmem, nil, false) memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)

} else {

p = mallocgc(capmem, et, true)

if lenmem > 0 && writeBarrier.enabled { bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-

et.Size_+et.PtrBytes)

}

}

memmove(p, oldPtr, lenmem)

  1. 返回新的 Slice。代码如下:

return slice{p, newLen, newcap}

总结

Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?,可以?便地进?增加、删除、截取等操作。Slice 的扩容是指 Slice的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容 Slice 的容量。

参考阅读

Go 语?设计与实现之 切?

代码如下:

return slice{p, newLen, newcap}

总结

Slice 是 Go 语?中的?个重要数据类型,它是对数组的?个抽象,提供了更加强?的功能。Slice 本身并不是?个数组,它指向底层数组。Slice 的功能很强?,可以?便地进?增加、删除、截取等操作。Slice 的扩容是指 Slice的?度超过了 Slice 的容量。当 Slice 的?度超过了 Slice 的容量时,Go 语?会?动扩容 Slice 的容量。

参考阅读

Go 语?设计与实现之 切?

Tags:

最近发表
标签列表