Go 1.20新特性Arena手动内存管理试用

1,278 阅读2分钟

就在昨天Go 1.20已正式发布,有一项实验特性Arena值得关注

截屏2023-02-03 20.13.08.png 使用这个功能可以手动管理内存的分配和释放,减少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的推出,可以让程序员写出性能更好的代码,但是也可能发生更多意想不到的错误