就在昨天Go 1.20已正式发布,有一项实验特性Arena值得关注
使用这个功能可以手动管理内存的分配和释放,减少GC的压力,提升程序性能
编写程序测试
package main
import (
"arena"
)
type myStruct struct {
val int
}
func main() {
// 创建一个Arena对象
Arena := arena.NewArena()
// 对比通过new初始化以及通过Arena初始化
p1 := foo[int]()
p2 := newStaticPointer[int](Arena)
*p2 = 10
println(*p1)
println(*p2)
slice1 := foo2[int](5)
slice2 := newStaticSlice[int](Arena, 5)
slice2[0] = 20
println(slice1[0])
println(slice2[0])
p3 := foo[myStruct]()
p4 := newStaticPointer[myStruct](Arena)
p4.val = 30
println(p3.val)
println(p4.val)
// 调用 Free() 后内存会立即释放,使用释放后的Arena会导致panic
Arena.Free()
newStaticPointer[int](Arena)
}
func foo[T any]() *T {
return new(T)
}
func foo2[T any](len int, cap ...int) []T {
c := len
for _, v := range cap {
c = v
break
}
return make([]T, len, c)
}
func newStaticPointer[T any](a *arena.Arena) *T {
t := arena.New[T](a)
return t
}
func newStaticSlice[T any](a *arena.Arena, len int, cap ...int) []T {
c := len
for _, v := range cap {
c = v
break
}
ts := arena.MakeSlice[T](a, len, c)
return ts
}
运行结果
10
0
20
0
30
panic: ...
编译时需要加入参数开启实验功能
go build xxx.go -tags goexperiment.arenas
Benchmark测试
package main
import (
"arena"
"testing"
)
func BenchmarkAlloc(b *testing.B) {
Arena := arena.NewArena()
b.ResetTimer()
for i := 0; i < b.N; i++ {
newStaticPointer[int](Arena)
}
}
func BenchmarkFoo(b *testing.B) {
for i := 0; i < b.N; i++ {
foo[int]()
}
}
运行结果
BenchmarkAlloc-8 76254412 19.53 ns/op 8 B/op 0 allocs/op
BenchmarkFoo-8 74731878 16.34 ns/op 8 B/op 1 allocs/op
PASS
ok test 3.813s
验证
加入逃逸分析
-gcflags '-m -l'
运行结果
# test
./main.go:37:12: new(<node DYNAMICTYPE>) escapes to heap
./main.go:47:13: make([]go.shape.int, len, c) escapes to heap
./main.go:37:12: new(<node DYNAMICTYPE>) escapes to heap
可以看到通过new或make方法初始化会发生逃逸,而通过Arena初始化的变量并没有发生逃逸,说明通过Arena初始化的对象直接分配在了堆上
原理(猜测)
Arena从一个连续的内存区域分配一组内存对象的方式,比一般的内存分配更有效率,也可以一次性释放,GC此时只需要检测一个Arena对象,大大减少了每次GC需要扫描的变量
Go最近的更新可以说比较激进,从泛型的添加到手动内存管理仅仅经过了一年,Arena的推出,可以让程序员写出性能更好的代码,但是也可能发生更多意想不到的错误