zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Animation(三)
    布局
    AutoCommpleteText
    PHP数组中常用函数
    Animation(四)
    转:JAVA内存映射文件
    Ubuntu安装jdk
    转:Java NIO 详解
    转:长连接与短连接
    Direct or Nondirect ByteBuffer
  • 原文地址:https://www.cnblogs.com/weirwei/p/15491841.html
Copyright © 2011-2022 走看看