返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

2.6.1 固定分配器

发布于 2024-10-12 19:15:56 字数 5629 浏览 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*
	specialReachableAlloc fixalloc // allocator for specialReachable
	arenaHintAlloc        fixalloc // allocator for arenaHints    
}

这其中,我们最为熟悉的是管理内存块的 mspan 对象。不同于 mheap 唯一实例,这几个字段各自持有独立固定分配器。在堆初始化方法里,分别被设定不同的分配属性。

// Initialize the heap.
func (h *mheap) init() {
	h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys)
	h.cachealloc.init(unsafe.Sizeof(mcache{}), nil, nil, &memstats.mcache_sys)
	h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), nil, nil, &memstats.other_sys)
	h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys)
	h.specialReachableAlloc.init(unsafe.Sizeof(specialReachable{}), nil, nil, &memstats.other_sys)
	h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys)
}

初始化

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

// 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
	...
    stat   *sysMemStat
    zero   bool // zero allocations
}
// Initialize f to allocate objects of the given size,
// using the allocator to obtain chunks of memory.

func (f *fixalloc) init(size uintptr, first func(arg, p unsafe.Pointer), arg unsafe.Pointer, stat *sysMemStat) {

	f.size = size
	f.first = first
	f.arg = arg
    ...
    f.stat = stat
    f.zero = true
}

分配

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

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

type fixalloc struct {
	list   *mlink
    
	chunk  uintptr
	nchunk uint32  // bytes remaining in current chunk
	nalloc uint32  // size of new chunks in bytes
    
	inuse  uintptr // in-use bytes now
}

分配时优先从复用链表提取,不足再从后备内存分割。如此看来,后备内存 chunk 类似 mheap 在三级结构中地位。另外,关联函数仅在分割新内存单元时执行,很适合做些记录性工作。

func (f *fixalloc) alloc() unsafe.Pointer {

    // 尝试从复⽤链表提取。
	if f.list != nil {
		v := unsafe.Pointer(f.list)
		f.list = f.list.next
		f.inuse += f.size
		if f.zero {
			memclrNoHeapPointers(v, f.size)
		}
		return v
	}
    
    // 如果后备资源不足,则重新获取 (16 KB)。
	if uintptr(f.nchunk) < f.size {
		f.chunk = uintptr(persistentalloc(uintptr(f.nalloc), 0, f.stat))
		f.nchunk = f.nalloc
	}

    // 从后备内存块分割。
	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)
	f.inuse += f.size
    
	return v
}
// mheap.go

// allocMSpanLocked allocates an mspan object.
func (h *mheap) allocMSpanLocked() *mspan {
    (*mspan)(h.spanalloc.alloc())
}

回收

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

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

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

手工调用,非 GC 回收。

// mheap.go

func (h *mheap) freeSpanLocked(s *mspan, typ spanAllocType) {
	s.state.set(mSpanDead)
	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 是空结构,包含一个计算偏移地址的方法。

// malloc.go

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 // per-P to avoid mutex
}
// malloc.go

var globalAlloc struct {
	mutex
	persistentAlloc
}

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

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

// malloc.go

// Wrapper around sysAlloc that can allocate small chunks.
// There is no associated free operation.

func persistentalloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer {
	var p *notInHeap
	p = persistentalloc1(size, align, sysStat)
	return unsafe.Pointer(p)
}
// persistentChunkSize is the number of bytes we allocate when we grow
// a persistentAlloc.
const persistentChunkSize = 256 << 10


func persistentalloc1(size, align uintptr, sysStat *sysMemStat) *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))
	}
    
    // 切分。
	p := persistent.base.add(persistent.off)
	persistent.off += size
    
	return p
}

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

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

发布评论

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