go的基础数据结构 slice源码阅读
1.slice(runtime/slice)
1.1slice结构体
type slice struct {array unsafe.Pointer // 指向底层数组的指针len int // 切片长度cap int // 切片容量
}type notInHeapSlice struct {array *notInHeap // 非堆内存切片len intcap int
}
notInHeapSlice 的作用
notInHeapSlice 是一个特殊的切片结构体,它的主要作用是:
1. 非堆内存切片
这是一个由 internal/runtime/sys.NotInHeap 内存支持的切片,与普通的 slice 结构体不同,notInHeapSlice 的底层数组指向的是非堆内存区域
2.用途
从代码中的使用情况可以看出,notInHeapSlice 主要用于:
内存管理器的内部数据结构:如 mbitmap.go、mpagealloc_*.go、mheap.go 等;
运行时系统的底层组件:如内存分配器、垃圾收集器等;避免垃圾收集器的干扰:这些内存区域不需要被 GC 管理
3.性能优势
避免写屏障:指向非堆内存的指针可以省略写屏障,提高性能
内存管理控制:运行时可以完全控制这些内存的分配和释放
避免 GC 扫描:这些内存不会被垃圾收集器扫描,减少 GC 开销
1.2 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 {// 错误处理逻辑panicmakeslicelen() // 长度超出范围panicmakeslicecap() // 容量超出范围}return mallocgc(mem, et, true) // 分配内存
}
计算所需内存:元素大小 × 容量
检查溢出、内存限制和参数有效性
使用mallocgc进行垃圾回收友好的内存分配
1.3 makeslicecopy函数
将切片进行复制转移
// makeslicecopy 分配一个类型为 "et"、长度为 "tolen" 的切片,
// 然后从 "from" 中复制 "fromlen" 个类型为 "et" 的元素到新分配的内存中。
func makeslicecopy(et *_type, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer {var tomem, copymem uintptrif uintptr(tolen) > uintptr(fromlen) {var overflow booltomem, overflow = math.MulUintptr(et.Size_, uintptr(tolen))if overflow || tomem > maxAlloc || tolen < 0 {panicmakeslicelen()}copymem = et.Size_ * uintptr(fromlen)} else {// fromlen 是已知有效的长度,并且大于等于 tolen,// 因此 tolen 作为目标切片长度也有效(因为元素宽度相同)。tomem = et.Size_ * uintptr(tolen)copymem = tomem}var to unsafe.Pointerif !et.Pointers() {to = mallocgc(tomem, nil, false)if copymem < tomem {// 清除未初始化的内存区域(无指针类型无需GC扫描)memclrNoHeapPointers(add(to, copymem), tomem-copymem)}} else {// 不能使用 rawmem(跳过内存清零),因为GC可能会扫描未初始化的内存to = mallocgc(tomem, et, true)if copymem > 0 && writeBarrier.enabled {// 仅标记源指针(目标切片已通过分配清零)// 传入类型是优化操作,因为源和目标都只包含完整et类型值// 详见 bulkBarrierPreWrite 注释bulkBarrierPreWriteSrcOnly(uintptr(to), uintptr(from), copymem, et)}}if raceenabled {callerpc := sys.GetCallerPC()pc := abi.FuncPCABIInternal(makeslicecopy)// 竞态检测:记录内存读取范围racereadrangepc(from, copymem, callerpc, pc)}if msanenabled {// 内存安全检测:标记读取区域msanread(from, copymem)}if asanenabled {// 地址消毒检测:标记读取区域asanread(from, copymem)}// 内存复制操作memmove(to, from, copymem)return to
}
1.4核心扩容策略
newLen = oldLen+num
即 sli = append(sli, make([]int, 1000)) 那么num即是1000 oldLen即是cap(sli)
func nextslicecap(newLen, oldCap int) int {newcap := oldCapdoublecap := newcap + newcapif newLen > doublecap {return newLen // 如果新长度超过2倍容量,直接使用新长度}const threshold = 256if oldCap < threshold {return doublecap // 小切片:容量翻倍}// 大切片:平滑过渡到1.25倍增长for {newcap += (newcap + 3*threshold) >> 2 // 约1.25倍增长if uint(newcap) >= uint(newLen) {break}}return newcap
}