擴容
slice 會遷移到新的內存位置,新底層數組的長度也會增加,這樣就可以放置新增的元素。同時,為了應對未來可能再次發生的 append 操作,新的底層數組的長度,也就是新 slice 的容量是留了一定的 buffer 的。否則,每次添加元素的時候,都會發生遷移,成本太高。
新 slice 預留的 buffer 大小是有一定規律的。
1.18之前
當原 slice 容量小于 1024 的時候,新 slice 容量變成原來的 2 倍;原 slice 容量超過 1024,新 slice 容量變成原來的1.25倍。
func growslice(et *_type, old slice, cap int) slice {// ……newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {if old.len < 1024 {newcap = doublecap} else {for newcap < cap {newcap += newcap / 4}}}// ……capmem = roundupsize(uintptr(newcap) * ptrSize)newcap = int(capmem / ptrSize)
}
1.18之后
當原slice容量(oldcap)小于256的時候,新slice(newcap)容量為原來的2倍;原slice容量超過256,新slice容量newcap = oldcap+(oldcap+3*256)/4。
func growslice(et *_type, old slice, cap int) slice {// ……newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {const threshold = 256if old.cap < threshold {newcap = doublecap} else {for 0 < newcap && newcap < cap {// Transition from growing 2x for small slices// to growing 1.25x for large slices. This formula// gives a smooth-ish transition between the two.newcap += (newcap + 3*threshold) / 4}if newcap <= 0 {newcap = cap}}}// ……capmem = roundupsize(uintptr(newcap) * ptrSize)newcap = int(capmem / ptrSize)
}
從小切片的2倍增長轉變為大切片的1.25倍增長,這個公式在兩者之間提供了一個平穩的過渡。
調整之后,隨著切片容量的變大,增長比例逐漸向著1.25進行靠攏。slice擴容整體的增長曲線變得更加平滑。
擴容時除了擴容成指定大小之外,還做了內存對齊,進行內存對齊之后,新slice的容量 >= newcap。