Arena方案
Arena 方案是 Go 语言中一种用于内存管理的技术,在 Go 1.20 版本引入了 arena
包,需要开启goexperiment.arenas实验特性来启用。Arena 本质上是一个连续的内存区域,你可以在这个区域内分配对象,并且这些对象的生命周期会与 Arena 绑定。在Google内部的实验中,arena可以节省15%的CPU和内存开销。
arena
包提供了一种能力,能够为一组 Go 语言的值分配内存,并可以安全地一次性手动释放这些内存空间。这个功能的目的在于提高效率:在垃圾回收(GC)之前手动释放内存,可以延迟垃圾回收的周期。垃圾回收周期不那么频繁,意味着垃圾回收器占用 CPU 的开销也会不那么频繁。
这个包中的功能主要体现在 Arena
类型中。Arena 会一次性分配大块内存,所以如果只是分配少量的小对象,使用 Arena 也不是最优的方案。Arena 最适合批量使用的场景,每次使用时所分配的内存量通常在MB级别。
注意,由于支持这种手动的内存分配,对于常规的 Go 对象,可能会出现释放后使用(use-after-free,也就是野指针)的错误用法,这种用法,目前官方没有严格保证安全性。

如何使用Arena Api
Arena的Api直接位于src目录下,是go sdk的一级package。

Arena的数据结构非常简单,只有一个指针,指向arena所持有的内容块。
rust
type Arena struct {
a unsafe.Pointer
}
然而实际在runtime里,Arena的结构也比较简单,主要的两个字段:
- fullList *mspan,完全使用的chunk。
- active *mspan,当前正在使用的chunk。
rust
type userArena struct {
fullList *mspan
active *mspan
refs []unsafe.Pointer
defunct atomic.Bool
}
开放的几个函数和方法包括:
NewArena
- NewArena 新建Arena,用于管理一块连续的内存空间,注意Arena指针的管理依然遵循GC的原则,也就是如果Arena自身不被引用,那么就会被GC回收掉,所以可能得借助runtime.KeepAlive用于保活。
csharp
// NewArena allocates a new arena.
func NewArena() *Arena {
return &Arena{a: runtime_arena_newArena()}
}
Free
- .Free(),释放整个Arena占有的空间,这里释放只清理了元信息,不会对Arena持有的完整内存空间做清理操作。
scss
// Free frees the arena (and all objects allocated from the arena) so that
// memory backing the arena can be reused fairly quickly without garbage
// collection overhead. Applications must not call any method on this
// arena after it has been freed.
func (a *Arena) Free() {
runtime_arena_arena_Free(a.a)
a.a = nil
}
注意一个arena只能调用一次Free,如果调用2次,会发生panic。

New
- New,泛型函数,从给定的Arena中申请T类型的空间,返回*T类型。
scss
// New creates a new *T in the provided arena. The *T must not be used after
// the arena is freed. Accessing the value after free may result in a fault,
// but this fault is also not guaranteed.
func New[T any](a *Arena) *T {
return runtime_arena_arena_New(a.a, reflectlite.TypeOf((*T)(nil))).(*T)
}
MakeSlice
- MakeSlice,泛型函数,类似New,不过申请的是数组的空间。
go
// MakeSlice creates a new []T with the provided capacity and length. The []T must
// not be used after the arena is freed. Accessing the underlying storage of the
// slice after free may result in a fault, but this fault is also not guaranteed.
func MakeSlice[T any](a *Arena, len, cap int) []T {
var sl []T
runtime_arena_arena_Slice(a.a, &sl, cap)
return sl[:len]
}
Clone
- Clone,将T从Arena的空间中,浅拷贝一份到堆中,适用于对象需要从Arena中逃逸的场景,避免直接引用原始对象导致野指针问题,注意T只能是指针、字符串或切片。
scss
// Clone makes a shallow copy of the input value that is no longer bound to any
// arena it may have been allocated from, returning the copy. If it was not
// allocated from an arena, it is returned untouched. This function is useful
// to more easily let an arena-allocated value out-live its arena.
// T must be a pointer, a slice, or a string, otherwise this function will panic.
func Clone[T any](s T) T {
return runtime_arena_heapify(s).(T)
}
Api的源码非常简单,但是很多核心逻辑,其实是用linkname实现在了runtime中,比如我们上边介绍的几个Api,以及反射包中的Api reflect.ArenaNew。
swift
在入参的Arena中申请一个typ对象并返回
//go:linkname reflect_arena_New reflect.arena_New
func reflect_arena_New(a *Arena, typ any) any {
return runtime_arena_arena_New(a.a, typ)
}
//go:linkname runtime_arena_newArena
func runtime_arena_newArena() unsafe.Pointer
//go:linkname runtime_arena_arena_New
func runtime_arena_arena_New(arena unsafe.Pointer, typ any) any
// Mark as noescape to avoid escaping the slice header.
//
//go:noescape
//go:linkname runtime_arena_arena_Slice
func runtime_arena_arena_Slice(arena unsafe.Pointer, slice any, cap int)
//go:linkname runtime_arena_arena_Free
func runtime_arena_arena_Free(arena unsafe.Pointer)
//go:linkname runtime_arena_heapify
func runtime_arena_heapify(any) any
但是Arena本质上还是堆上的一段空间,如何避免被GC回收掉,如何避免GC的扫描成本,也是非常复杂的,这部分的完整实现,在runtime中有部分独立的实现,并且用userArena集成在GC整体的实现中,在runtime中的独立实现在arena.go中,go1.21的版本是1000行代码。

适用场景
官方的介绍里适用在一段时间内申请大量 内存 空间 ,然后统一释放掉,所以其实几个适用场景也比较明确,例如:
- http/rpc handler等无状态的访问接口,io密集型的服务,在处理完请求并返回后,所有在请求期间的对象都可以统一释放掉
- json/protobuf/thrift等序列化框架,在处理完序列化/反序列化后,一些中间对象其实都可以统一释放掉
不适用的场景其实也就是不需要大量对象申请、统一释放的场景,例如
- 也就是cpu密集型的服务,不涉及大量对象的申请
- cache型的服务,对象的生命周期不固定
风险
Arena的风险其实也是手动内存管理语言(例如C/C++)可能会遇到的一些问题:
- 内存泄露:因为依赖手动内存管理,若使用不当,未及时释放Arena,会导致内存泄露。
- 野指针:在Arena释放后依然引用Arena分配的空间,会导致指针访问有问题的空间。
参考文档
- proposal: github.com/golang/go/i...
- source code:github.com/golang/go/r...