golang 源码分析之slice

切片

切片就是动态数组,它的长度并不固定,我们可以随意向切片中追加元素,而切片会在容量不足时自动扩容。
cmd/compile/internal/types.NewSlice

// NewSlice returns the slice Type with element type elem.
func NewSlice(elem *Type) *Type {
	//返回的结构体 TSLICE 中的 Extra 字段是一个只包含切片内元素类型的 Slice{Elem: elem} 结构,
	// 也就是说切片内元素的类型是在编译期间确定的,
	// 编译器确定了类型之后,会将类型存储在 Extra 字段中帮助程序在运行时动态获取。
	if t := elem.Cache.slice; t != nil {
		if t.Elem() != elem {
			Fatalf("elem mismatch")
		}
		return t
	}

	t := New(TSLICE)
	t.Extra = Slice{Elem: elem}
	elem.Cache.slice = t
	return t
}

运行时切片由如下的 SliceHeader 结构体表示

reflect.SliceHeader

type SliceHeader struct {
	Data uintptr    //指向数组的指针
	Len  int        //当前切片的长度
	Cap  int        //当前切片的容量
}

初始化

arr[0:3] or slice[0:3]      //通过下标的方式获得数组或者切片的一部分
slice := []int{1, 2, 3}     //使用字面量初始化新的切片  //这里的新建编译期会先新建出数组然后走切割
slice := make([]int, 10)    //使用关键字 make 创建切片

通过make来新建,运行期新建

runtime.slice

func makeslice64(et *_type, len64, cap64 int64) unsafe.Pointer {
	//这个判断的方式有点骚
	len := int(len64)
	if int64(len) != len64 {
		panicmakeslicelen()
	}
	cap := int(cap64)
	if int64(cap) != cap64 {
		panicmakeslicecap()
	}

	return makeslice(et, len, cap)
}
func makeslice(et *_type, len, cap int) unsafe.Pointer {
	// 计算当前切片需要用到的内存空间
	mem, overflow := math.MulUintptr(et.size, uintptr(cap))
	//overflow 溢出 
	// mem > maxAlloc 申请的内容大于最大可申请的内存
	if overflow || mem > maxAlloc || len < 0 || len > cap {
		// 如果cap溢出 或者 大于最大可申请的内存,尝试用len计算
		mem, overflow := math.MulUintptr(et.size, uintptr(len))
		if overflow || mem > maxAlloc || len < 0 {
			panicmakeslicelen()
		}
		panicmakeslicecap()
	}
	return mallocgc(mem, et, true)
}

mallocgc 方法

  //分配一个大小为mem字节的对象。 
  // 从per-P缓存的空闲列表中分配小对象。 
  // 从堆直接分配大对象(mem> 32 kB)。
  // ps:看其源码如果mem等于0,直接返回 return unsafe.Pointer(&zerobase)
  // var zerobase uintptr
    mallocgc(mem, et, true)

growslice 切片扩容的方法

et 类型
old 旧的切片
cap 所需的最小容量
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))
	}
	//是否开启了msan,探测是否读未初始化的内存
	if msanenabled {
		msanread(old.array, uintptr(old.len*int(et.size)))
	}
    // 如果所需容量小于就切片的容量
	if cap < old.cap {
		panic(errorString("growslice: cap out of range"))
	}
    // 如果对应的类型size是0,那就是zerobase 不需要申请内存
	if et.size == 0 {
		return slice{unsafe.Pointer(&zerobase), old.len, cap}
	}
    // 计算需要多少容量
	newcap := old.cap
	doublecap := newcap + newcap
	// 如果所需容量大于old的2倍,那就取所需的,
	// 否则如果old的容量小于1024 取old的2倍
	// 如果old大于1024 每次增加 4分1 直到满足条件
	// 例如 old 1600 need 3000
	//  第一次 2000<3000 第二次 2000+2000/4=2500<3000 第三次2500+2500/4 =3125>3000
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
    
	var overflow bool
	var lenmem, newlenmem, capmem uintptr
	// 计算容量
	// 这里都是细节啊!!
	// 如果size是1 暂时没发现1代表啥哈哈 不需要做乘除的运算
	// 如果size是指针类型的,做相关计算
	// 如果大小是2的倍数的,走了一些计算的优化
	// 其他的不能优化走默认去了
	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)
	}
    // 溢出直接panic
	if overflow || capmem > maxAlloc {
		panic(errorString("growslice: cap out of range"))
	}

	var p unsafe.Pointer
	// 切片不是指针类型调用memclrNoHeapPointers将超出切片当前长度的位置清空,
	// 仅清除不会被覆盖的位置,细节啊...!!!!!
	if et.ptrdata == 0 {
		p = mallocgc(capmem, nil, false)
		// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
		// Only clear the part that will not be overwritten.
		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 old.array since we know the destination slice p
			// only contains nil pointers because it has been cleared during alloc.
			bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
		}
	}
	// 旧移动到新
	memmove(p, old.array, lenmem)
    
	return slice{p, old.len, newcap}
}

slicecopy: 运行时拷贝切片

toPtr 目标切片
toLen 目标切片长度
fmPtr 来源切片
fmLen 来源切片长度
width 单个元素的大小
func slicecopy(toPtr unsafe.Pointer, toLen int, fmPtr unsafe.Pointer, fmLen int, width uintptr) int {
	if fmLen == 0 || toLen == 0 {
		return 0
	}

	n := fmLen
	if toLen < n {
		n = toLen
	}

	if width == 0 {
		return n
	}

	if raceenabled {
		callerpc := getcallerpc()
		pc := funcPC(slicecopy)
		racereadrangepc(fmPtr, uintptr(n*int(width)), callerpc, pc)
		racewriterangepc(toPtr, uintptr(n*int(width)), callerpc, pc)
	}
	if msanenabled {
		msanread(fmPtr, uintptr(n*int(width)))
		msanwrite(toPtr, uintptr(n*int(width)))
	}

	size := uintptr(n) * width
	// size 等于1指针
	if size == 1 { // common case worth about 2x to do here
		// TODO: is this still worth it with new memmove impl?
		*(*byte)(toPtr) = *(*byte)(fmPtr) // known to be a byte pointer
	} else {
		// 整块内存拷贝
		memmove(toPtr, fmPtr, size)
	}
	return n
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值