go 切片的扩容

slice

type slice struct {
   array unsafe.Pointer
   len   int
   cap   int
}
func makeslice(et *_type, len, cap int) unsafe.Pointer {
  // 1. 计算需要申请的容量,并判断是否内存溢出
	mem, overflow := math.MulUintptr(et.size, uintptr(cap))
  
  // 2. 内存溢出原因
	if overflow || mem > maxAlloc || len < 0 || len > cap {
		// 计算slice所需内存通过MulUintptr来实现的
		mem, overflow := math.MulUintptr(et.size, uintptr(len))
		if overflow || mem > maxAlloc || len < 0 {
			panicmakeslicelen()
		}
		panicmakeslicecap()
	}

  // 3. 进行内存分配
	return mallocgc(mem, et, true)
}
func growslice(et *_type, old slice, cap int) slice {

  ...
  
	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
     // 当原切片长度小于1024时,新切片的容量会直接翻倍。而当原切片的容量大于等于1024时,会反复地增加25%,直到新容量超过所需要的容量
		if old.cap < 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
	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)
	}

	...
  
}
package runtime
// 内存对齐的过程,为了避免造成过多的内存碎片
func roundupsize(size uintptr) uintptr {
  // size=1600*8=12800<32768
  if size < _MaxSmallSize {
    // 12800<=0
    if size <= smallSizeMax-8 {
      return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
    } else {
      return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])//size_to_class128[92]= 56
      //class_to_size[56]=13568
      //13568/8=1696
    }
  }
  if size+_PageSize < size {
    return size
  }
  return round(size, _PageSize)
}
const _MaxSmallSize   = 32768
const smallSizeDiv    = 8
const smallSizeMax    = 1024
const largeSizeDiv    = 128

总结

slice容量的扩容规则:当原slice的cap小于1024时,新slice的cap变为原来的2倍;原slice的cap大于1024时,新slice变为原来的1.25倍,按照这个规则扩充后,还会进行内存对齐操作。

回到开头,为什么确定切片容量的程序效率更高?因为他省去了扩容的步骤。

扩展:内存对齐的目的

image-20211101012421747

假设CPU的内存读写单位为4字节

在内存对齐和非对齐情况下,读取变量a都仅需要读取一次。

在内存对齐情况下,如果要读取变量b,则仅需要读取1次,即第二部分(4-7);而非对齐情况下,则需要读取2次,即第一部分(0-3)取后3个字节,第二部分取前1个字节,然后用或操作拼接成变量b。

因此,内存对齐在某些情况下可以减少内存的读取次数,提高性能,是一种空间换时间的策略。
参考:https://segmentfault.com/a/1190000037781000

posted @ 2021-11-01 01:29  weirwei  阅读(388)  评论(0编辑  收藏  举报