赞
踩
简单来说,切片就是一种简化版的动态数组。因为动态数组的长度是不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组的类型和操作都不够灵活,因此在Go代码中数组使用的并不多。而切片则使用得相当广泛。
注意:我会把源码中每个方法的作用都注释出来,可以参考注释进行理解。
在看源码之前,我们先来看一个简单的例子
func main() {
slice:=make([]int,0) //第6行
slice=append(slice,1)//第7行
}
通过make创建一个长度为0,容量为0的slice,接着通过append函数向slice追加元素
接下来,我们就来分析slice创建以及追加的过程
由于append() 是内置函数,所以我们只能通过查看它的汇编语言才能知道append() 发生了什么
内置函数:Go 语言拥有一些不需要进行导入操作就可以使用的内置函数
用以下命令查看的Go语言程序对应的伪汇编代码:
go tool compile -S slice.go
其中go tool compile命令用于调用Go语言提供的底层命令工具,其中-S参数表示输出汇编格式。
... 0x0028 00040 (slice.go:6) PCDATA $2, $1 0x0028 00040 (slice.go:6) PCDATA $0, $0 0x0028 00040 (slice.go:6) LEAQ type.int(SB), AX 0x002f 00047 (slice.go:6) PCDATA $2, $0 0x002f 00047 (slice.go:6) MOVQ AX, (SP) 0x0033 00051 (slice.go:6) XORPS X0, X0 0x0036 00054 (slice.go:6) MOVUPS X0, 8(SP) 0x003b 00059 (slice.go:6) CALL runtime.makeslice(SB) 0x0040 00064 (slice.go:6) PCDATA $2, $1 0x0040 00064 (slice.go:6) MOVQ 24(SP), AX 0x0045 00069 (slice.go:7) PCDATA $2, $2 0x0045 00069 (slice.go:7) LEAQ type.int(SB), CX 0x004c 00076 (slice.go:7) PCDATA $2, $1 0x004c 00076 (slice.go:7) MOVQ CX, (SP) 0x0050 00080 (slice.go:7) PCDATA $2, $0 0x0050 00080 (slice.go:7) MOVQ AX, 8(SP) 0x0055 00085 (slice.go:7) XORPS X0, X0 0x0058 00088 (slice.go:7) MOVUPS X0, 16(SP) 0x005d 00093 (slice.go:7) MOVQ $1, 32(SP) 0x0066 00102 (slice.go:7) CALL runtime.growslice(SB) 0x006b 00107 (slice.go:7) PCDATA $2, $1 0x006b 00107 (slice.go:7) MOVQ 40(SP), AX 0x0070 00112 (slice.go:7) MOVQ 48(SP), CX 0x0075 00117 (slice.go:7) MOVQ 56(SP), DX 0x007a 00122 (slice.go:7) MOVQ $1, (AX) ...
其中,我们只需要关注CALL指令,看它调用了什么。
0x003b 00059 (slice.go:6) CALL runtime.makeslice(SB)
slice.go第六行,调用了makeslice函数,则makeslice函数是用于初始化slice的
0x0066 00102 (slice.go:7) CALL runtime.growslice(SB)
同理,growslice函数是用于追加元素的
makeslice
接下来,我们看看在go的sdk里面slice是如何实现这些函数的
runtime\slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
slice 的底层结构定义非常直观,指向底层数组的指针,当前长度 len 和当前 slice 的 cap。
再来看看makeslice函数
func makeslice(et *_type, len, cap int) unsafe.Pointer { // 获取需要申请的内存大小 mem, overflow := math.MulUintptr(et.size, uintptr(cap)) if overflow || mem > maxAlloc || len < 0 || len > cap { // NOTE: Produce a 'len out of range' error instead of a // 'cap out of range' error when someone does make([]T, bignumber). // 'cap out of range' is true too, but since the cap is only being // supplied implicitly, saying len is clearer. // See golang.org/issue/4085. mem, overflow := math.MulUintptr(et.size, uintptr(len)) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() } // 分配内存 // 小对象从当前P 的cache中空闲数据中分配 // 大的对象 (size > 32KB) 直接从heap中分配 return mallocgc(mem, et, true) }
实在是太简单了,没啥可说的。mallocgc 函数会根据申请的内存大小,去对应的内存块链表上找合适的内存来进行分配,是 Go 自己改造的 tcmalloc 那一套。
func growslice(et *_type, old slice, cap int) slice { //扫描内存 if raceenabled { callerpc := getcallerpc() racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice)) } if msanenabled { msanread(old.array, uintptr(old.len*int(et.size))) } //容量判断 if cap < old.cap { panic(errorString("growslice: cap out of range")) } //如果存储的类型空间为0, 比如说 []struct{}, 数据为空,长度不为空 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 old.array in this case. return slice{unsafe.Pointer(&zerobase), old.len, cap} } newcap := old.cap doublecap := newcap + newcap //如果新的容量大于旧的两倍,则直接扩容到新的容量 if cap > doublecap { newcap = cap } else { //当新的容量不大于旧的两倍 // 如果旧长度小于1024,那扩容到旧的两倍 if old.len < 1024 { newcap = doublecap } else { //否则扩容到旧的1.25倍 for 0 < newcap && newcap < cap { newcap += newcap / 4 } //检查newCap是否溢出 if newcap <= 0 { newcap = cap } } } var overflow bool var lenmem, newlenmem, capmem uintptr // 为了加速计算 // 对于不同的slice元素大小,选择不同的计算方法 // 获取需要申请的内存大小。 switch { case et.size == 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem) case et.size == sys.PtrSize: lenmem = uintptr(old.len) * sys.PtrSize newlenmem = uintptr(cap) * sys.PtrSize capmem = roundupsize(uintptr(newcap) * sys.PtrSize) overflow = uintptr(newcap) > maxAlloc/sys.PtrSize newcap = int(capmem / sys.PtrSize) case isPowerOfTwo(et.size): //如果是二的倍数,用位移运算 var shift uintptr if sys.PtrSize == 8 { // Mask shift for better code generation. shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 } else { shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 } lenmem = uintptr(old.len) << shift newlenmem = uintptr(cap) << shift capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift) default: //默认用除法 lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) capmem = roundupsize(capmem) newcap = int(capmem / et.size) } // 判断是否会溢出 if overflow || capmem > maxAlloc { panic(errorString("growslice: cap out of range")) } var p unsafe.Pointer if et.kind&kindNoPointers != 0 { //为新的切片开辟容量为capmem的地址空间 p = mallocgc(capmem, nil, false) // 仅清除不会被覆盖的部分。 memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { // 注意:不能使用rawmem(这样可以避免内存清零),因为GC可以扫描未初始化的内存。 p = mallocgc(capmem, et, true) if writeBarrier.enabled { //因为我们知道目标切片p,所以仅将指针隐藏在old.array中 //仅包含nil指针,因为在分配过程中已将其清除。 bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem) } } //数据拷贝 memmove(p, old.array, lenmem) return slice{p, old.len, newcap} }
扩容规则:
slice 扩容必然会导致内存拷贝,如果是性能敏感的系统中,尽可能地提前分配好 slice 是较好的选择。
func slicecopy(to, fm slice, width uintptr) int { //从slice长度为0或者到slice长度为0 if fm.len == 0 || to.len == 0 { return 0 } n := fm.len if to.len < n { n = to.len } //长度为0,直接返回 if width == 0 { return n } //分析内存 if raceenabled { callerpc := getcallerpc() pc := funcPC(slicecopy) racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc) racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc) } if msanenabled { msanwrite(to.array, uintptr(n*int(width))) msanread(fm.array, uintptr(n*int(width))) } size := uintptr(n) * width if size == 1 { // 直接内存拷贝 *(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer } else { memmove(to.array, fm.array, size) } return n }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。