Go中new源码阅读笔记

172 阅读6分钟

本文Go基于1.21.4版本。

Go中的new是一个内置函数,它定义在builtin/builtin下。

内置函数

/*
Package builtin provides documentation for Go's predeclared identifiers.
The items documented here are not actually in package builtin
but their descriptions here allow godoc to present documentation
for the language's special identifiers.
*/
// `builtin` 包为`Go`的预声明标识符提供文档。
// 这里记录的内容实际上并不在`builtin`包中,
// 但它们的描述允许 `godoc` 提供该语言特殊标识符的文档。
package builtin

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
// `builtin` 包下`new`的内置函数,用于分配内存。
// 第一个参数是`类型`,而不是值,
// 返回的值是指向该类型新分配的`零值`的`指针`。

func new(Type) *Type

内置函数只定义了new的方法,但是没看到实现。那么实现位置是哪里呢?接下来目光移至runtime/malloc

malloc包

// implementation of new builtin
// compiler (both frontend and SSA backend) knows the signature
// of this function.
// `runtime/malloc`包下的`newobject`实现了`new`的内置定义。


func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.Size_, typ, true)
}

newobject 调用了mallocgc方法,这个方法也是slice,channel等内置定义的初始化方法。

mallocgc函数实现

这个方法有几百行,我挑选几段关键部分解读。

遇到GC标记结束会抛出异常

// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
// 分配字节大小的对象。 
// 小对象是从每个P缓存的可用列表中分配的。
// 大型对象(>32kB)直接从堆中分配。

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    if gcphase == _GCmarktermination {
       throw("mallocgc called with gcphase == _GCmarktermination")
    }

    if size == 0 {
       return unsafe.Pointer(&zerobase)
    }
    
    ...

_GCmarktermination 是什么呢?

// runtime/mgc.go
const (
    _GCoff             = iota // GC 未运行; 在后台扫描,写入屏障不可用(GC not running; sweeping in background, write barrier disabled)
    _GCmark                   // GC 标记根和工作节点:分配黑色,写入屏障可用 (GC marking roots and workbufs: allocate black, write barrier ENABLED)
    _GCmarktermination        // GC 标记终止:分配黑色,P协助GC,写入屏障可用(GC mark termination: allocate black, P's help GC, write barrier ENABLED)
)

此处可以看出,当GC处于_GCmarktermination时,做初始化动作会抛出异常。此处还没想明白,等找到解答后更新此处文档。

size表示的是当前对象所占用的字节数。

内存逃逸

// maxSmallSize是个常量,定义为32768(32kb)
// 此处如果检测占用大小大于32kb,会在堆上分配
if size <= maxSmallSize {
    if noscan && size < maxTinySize {
       // Tiny allocator.
       //
       // Tiny allocator combines several tiny allocation requests
       // into a single memory block. The resulting memory block
       // is freed when all subobjects are unreachable. The subobjects
       // must be noscan (don't have pointers), this ensures that
       // the amount of potentially wasted memory is bounded.
       
       // 微型分配器将多个小型分配请求组合到单个内存块中。
       // 当所有子对象都无法访问时,生成的内存块将被释放。
       // 子对象必须是 noscan(没有指针),这确保了潜在浪费的内存量是有限的
       
       
       // Size of the memory block used for combining (maxTinySize) is tunable.
       // Current setting is 16 bytes, which relates to 2x worst case memory
       // wastage (when all but one subobjects are unreachable).
       // 8 bytes would result in no wastage at all, but provides less
       // opportunities for combining.
       // 32 bytes provides more opportunities for combining,
       // but can lead to 4x worst case wastage.
       // The best case winning is 8x regardless of block size.
      
      // 用于组合的内存块的大小 (maxTinySize) 是可调的。
      // 当前设置为 16 字节,这与最坏情况下的 2 倍内存浪费有关
      // (当除一个子对象外的所有子对象都无法访问时)。
      // 8 个字节确实不会导致浪费,但提供的组合机会更少。
      // 32 字节提供了更多的组合机会,但可能导致 4 倍的最坏情况浪费。
      // 无论区块大小如何,最优匹配情况是 8 倍
       
       
       // Objects obtained from tiny allocator must not be freed explicitly.
       // So when an object will be freed explicitly, we ensure that
       // its size >= maxTinySize.
       
       // 从微型分配器获取的对象不可能通过显式释放。
       // 因此,当一个对象被显式释放时,我们确保其大小 >= maxTinySize
       
       
       // SetFinalizer has a special case for objects potentially coming
       // from tiny allocator, it such case it allows to set finalizers
       // for an inner byte of a memory block.
       
       // SetFinalizer 有一个特殊情况,用于可能来自微小分配器的对象
       // 在这种情况下,它允许为内存块的内部字节设置finalizer
       
       
       // The main targets of tiny allocator are small strings and
       // standalone escaping variables. On a json benchmark
       // the allocator reduces number of allocations by ~12% and
       // reduces heap size by ~20%.
       // 微型分配器的主要目标是小字符串和独立的转义变量。
       // 在 json 压力测试中,分配器将分配数量减少了 ~12%,
       // 并将堆大小减少了 ~20%。
       
       off := c.tinyoffset
       // Align tiny pointer for required (conservative) alignment.
       // 对齐微小的指针以进行必需的(保守)对齐
       if size&7 == 0 {
          off = alignUp(off, 8)
       } else if goarch.PtrSize == 4 && size == 12 {
          // Conservatively align 12-byte objects to 8 bytes on 32-bit
          // systems so that objects whose first field is a 64-bit
          // value is aligned to 8 bytes and does not cause a fault on
          // atomic access. See issue 37262.
          // TODO(mknyszek): Remove this workaround if/when issue 36606
          // is resolved.
          // 在 32 位系统上保守地将 12 字节对象与 8 字节对齐,
          // 以便第一个字段为 64 位值的对象与 8 字节对齐,
          // 并且不会在原子访问时导致错误。
          
          off = alignUp(off, 8)
       } else if size&3 == 0 {
          off = alignUp(off, 4)
       } else if size&1 == 0 {
          off = alignUp(off, 2)
       }
       if off+size <= maxTinySize && c.tiny != 0 {
          // The object fits into existing tiny block.
          // 该对象适合现有的小块
          x = unsafe.Pointer(c.tiny + off)
          c.tinyoffset = off + size
          c.tinyAllocs++
          mp.mallocing = 0
          releasem(mp)
          return x
       }
       // Allocate a new maxTinySize block.
       // 分配新的 maxTinySize 块
       span = c.alloc[tinySpanClass]
       v := nextFreeFast(span)
       if v == 0 {
          v, span, shouldhelpgc = c.nextFree(tinySpanClass)
       }
       x = unsafe.Pointer(v)
       (*[2]uint64)(x)[0] = 0
       (*[2]uint64)(x)[1] = 0
       // See if we need to replace the existing tiny block with the new one
       // based on amount of remaining free space.
       // 看看我们是否需要根据剩余的可用空间量用新的小块替换现有的小块
       if !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
          // Note: disabled when race detector is on, see comment near end of this function.
          // 注意:当竞争检测器打开时禁用,请参阅此功能末尾的注释
          c.tiny = uintptr(x)
          c.tinyoffset = size
       }
       size = maxTinySize
    } else {
       var sizeclass uint8
       if size <= smallSizeMax-8 {
          sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
       } else {
          sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
       }
       size = uintptr(class_to_size[sizeclass])
       spc := makeSpanClass(sizeclass, noscan)
       span = c.alloc[spc]
       v := nextFreeFast(span)
       if v == 0 {
          v, span, shouldhelpgc = c.nextFree(spc)
       }
       x = unsafe.Pointer(v)
       if needzero && span.needzero != 0 {
          memclrNoHeapPointers(x, size)
       }
    }
} else {
    shouldhelpgc = true
    // For large allocations, keep track of zeroed state so that
    // bulk zeroing can be happen later in a preemptible context.
    // 对于大型分配,请跟踪清零状态,以便以后可以在抢占式上下文中进行批量清零
    span = c.allocLarge(size, noscan)
    span.freeindex = 1
    span.allocCount = 1
    size = span.elemsize
    x = unsafe.Pointer(span.base())
    if needzero && span.needzero != 0 {
       if noscan {
          delayedZeroing = true
       } else {
          memclrNoHeapPointers(x, size)
          // We've in theory cleared almost the whole span here,
          // and could take the extra step of actually clearing
          // the whole thing. However, don't. Any GC bits for the
          // uncleared parts will be zero, and it's just going to
          // be needzero = 1 once freed anyway.
          
          // 从理论上讲,我们已经清除了这里几乎整个跨度,
          // 并且可以采取额外的步骤来实际清除整个事情。
          // 但是,不要。
          // 未清除部分的任何 GC 位都将为零,
          // 无论如何,一旦释放,它就会变成 needzero = 1
       }
    }
}

其他

new源码里还有垃圾回收相关代码,尚未读透彻,在未来有更深的感悟,再更新此文档。

总结

new是做了对应类型的初始化,并返回该类型的指针。如果初始化对象占用空间过大,会动态分配到堆,以达到最优的性能。