var p unsafe.Pointer if !et.Pointers() { p = mallocgc(capmem, nil, false) // The append() that calls growslice is going to overwrite from oldLen to newLen. // Only clear the part that will not be overwritten. // The reflect_growslice() that calls growslice will manually clear // the region not cleared here. 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 oldPtr since we know the destination slice p // only contains nil pointers because it has been cleared during alloc. // // It's safe to pass a type to this function as an optimization because // from and to only ever refer to memory representing whole values of // type et. See the comment on bulkBarrierPreWrite. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes, et) } } memmove(p, oldPtr, lenmem)
return slice{p, newLen, newcap}
GC 协作的设计:
区分指针和非指针类型:
非指针类型:mallocgc(..., nil, false) - 不需要 GC
扫描
指针类型:mallocgc(..., et, true) - 需要 GC 扫描和
write barrier
if et.Size_ == 0 { // append should not create a slice with nil pointer but non-zero len. // We assume that append doesn't need to preserve oldPtr in this case. return slice{unsafe.Pointer(&zerobase), newLen, newLen} }
对于 struct{}、[0]int 等零大小类型:
所有实例共享同一个地址 &zerobase
不分配任何实际内存
len 和 cap 的语义依然保持
5. 实践
5.1 预分配的重要性
1 2 3 4 5 6 7 8 9 10 11
// 差:多次扩容 var s []int for i := 0; i < 10000; i++ { s = append(s, i) }
// 好:一次分配 s := make([]int, 0, 10000) for i := 0; i < 10000; i++ { s = append(s, i) }