返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

2.6.1 固定分配器

发布于 2024-10-12 19:16:03 字数 4812 浏览 0 评论 0 收藏 0

前文所提内存,多指用户对象存储。

但编译后的程序,实际由用户和运行时两部分代码共同组成。

运行时相关操作,同样需要分配内存。只是这些系统对象不在保留区域分配,且采取不同生命周期和复用策略。

为此,专门设计了 FixAlloc 固定分配器。

从堆数据结构中,可以看到几个使用固定分配器的字段。

// mheap.go

type mheap struct {
    spanalloc             fixalloc  // allocator for span*
    cachealloc            fixalloc  // allocator for mcache*
    specialfinalizeralloc fixalloc  // allocator for specialfinalizer*
    specialprofilealloc   fixalloc  // allocator for specialprofile*
    arenaHintAlloc        fixalloc  // allocator for arenaHints
}

这其中,我们最为熟悉的是管理内存块的 mspan。

不同于 mheap 唯一实例,这几个字段各自持有独立固定分配器。

在堆初始化方法里,分别被设定不同的分配属性。

span 指代内存块,而 mspan 则是管理对象自身。

func (h *mheap) init() {
    h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, ...)
    h.cachealloc.init(unsafe.Sizeof(mcache{}), ...)
    h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), ...)
    h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), ...)
    h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), ...)
}

初始化

初始化时,除内存单元大小外,还可指定一关联函数和执行参数。

// mfixalloc.go

// FixAlloc is a simple free-list allocator for fixed size objects.
// Malloc uses a FixAlloc wrapped around sysAlloc to manage its
// MCache and MSpan objects.

type fixalloc struct {
    size   uintptr
    first  func(arg, p unsafe.Pointer) // called first time p is returned
    arg    unsafe.Pointer
}
func (f *fixalloc) init(size uintptr, first func(arg, p unsafe.Pointer), arg unsafe.Pointer, stat *uint64) {
    f.size = size
    f.first = first
    f.arg = arg
    f.list = nil
    f.chunk = 0
    f.nchunk = 0
    f.inuse = 0
    f.stat = stat
    f.zero = true
}

分配

从结构上看,固定分配器采用早期内存分配器设计思路。

以链表(list)管理回收可复用内存单元,另持一块待分割内存块(chunk)作为后备资源。

// mfixalloc.go

type fixalloc struct {
    list   *mlink
    
    chunk  uintptr
    nchunk uint32
}

分配时优先从复用链表提取,不足再从后备内存分割。

如此看来,后备内存类似堆在三级结构中地位。

另外,关联函数仅在分割新内存单元时执行,很适合做些记录性工作。

func (f *fixalloc) alloc() unsafe.Pointer {
    
    // 尝试从复⽤链表提取。
    if f.list != nil {
        v := unsafe.Pointer(f.list)
        f.list = f.list.next
        return v
    }
    
    // 如果后备资源不足,则重新获取 (16 KB)。
    if uintptr(f.nchunk) < f.size {
        f.chunk = uintptr(persistentalloc(_FixAllocChunk, 0, f.stat))
        f.nchunk = _FixAllocChunk
    }
    
    // 从后备内存块分割。
    v := unsafe.Pointer(f.chunk)
    
    // 如有关联函数,则执⾏。
    if f.first != nil {
        f.first(f.arg, v)
    }
    
    f.chunk = f.chunk + f.size
    f.nchunk -= uint32(f.size)
    
    return v
}
func (h *mheap) allocMSpanLocked() *mspan {
    return (*mspan)(h.spanalloc.alloc())
}

回收

回收操作仅将内存单元放回复用链表,并没有与释放相关的行为。

使用 FixAlloc 的几种管理对象,其数量不会太多,不释放物理内存影响不大。

func (f *fixalloc) free(p unsafe.Pointer) {
    v := (*mlink)(p)
    v.next = f.list
    f.list = v
}

手工调用,非 GC 回收。

// mheap.go

func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool) {
    h.freeMSpanLocked(s)
}

func (h *mheap) freeMSpanLocked(s *mspan) {
    h.spanalloc.free(unsafe.Pointer(s))
}

后备

实际上,后备内存不仅仅为固定分配器服务,还频繁出现在运行时其他部件中。

// malloc.go

type persistentAlloc struct {
    base *notInHeap
    off  uintptr
}

notInHeap 是空结构,包含一个计算偏移地址的方法。

type notInHeap struct{}

func (p *notInHeap) add(bytes uintptr) *notInHeap {
    return (*notInHeap)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + bytes))
}

基于性能考虑,线程(P)拥有独立后备内存。另以全局变量作为补充。

// runtime2.go

type p struct {
    palloc persistentAlloc
}
// malloc.go

var globalAlloc struct {
    mutex
    persistentAlloc
}

优先从当前线程内无锁提取。如果不足,再向操作系统申请。

向操作系统申请内存时,直接使用 sysAlloc,避开内存分配器。

// malloc.go

const persistentChunkSize = 256 << 10

func persistentalloc1(size, align uintptr, sysStat *uint64) *notInHeap {
    const (
        maxBlock = 64 << 10
    )
    
    // 如果⼤大⼩小超过 64 KB,直接向操作系统申请。
    if size >= maxBlock {
        return (*notInHeap)(sysAlloc(size, sysStat))
    }
    
    var persistent *persistentAlloc
    
    // 确定后备内存位置 (本地或全局)。
    if mp != nil && mp.p != 0 {
        persistent = &mp.p.ptr().palloc
    } else {
        persistent = &globalAlloc.persistentAlloc
    }
    
    // 如果内存不足,向操作系统申请 (256 KB)。
    if persistent.off+size > persistentChunkSize || persistent.base == nil {
        persistent.base = (*notInHeap)(sysAlloc(persistentChunkSize, &memstats.other_sys))
        persistent.off = alignUp(sys.PtrSize, align)
    }
    
    // 从后备内存切分。
    p := persistent.base.add(persistent.off)
    persistent.off += size
    
    return p
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文