大家好,我是煎鱼。

最近 Go1.20 中的手动办理内存受到了很多人的关注。众所周知,Go 是一门带废物收回(GC)的编程言语,能够进行主动的内存请求、开释等内存操作。

带 GC 能简化编程时的心智成本,也保证了内存的安全。咱们说 “一般”,也就是有例外。人们说六个,一般都有七个。

Go 的例外就呈现了。

Go1.20 arena

新版本 Go1.20,根据 Google 自身的需求,快速经过了实践,正式支撑了 arena,能够实现手动的内存办理(当前是实验性特性)。

现在能够经过 GOEXPERIMENT=arenas 环境变量启用:

GOEXPERIMENT=arenas go run main.go

该特功能够让程序员手动的从一个连续的内存区域请求、分配一组内存目标,也能够一次性的开释。

重点是能够手动办理内存。

供给的 arena API

  • NewArena:创立一个新的 arena 内存空间。
  • Free:开释 arena 及其相关目标。
  • New:根据 arena,创立新目标。
  • MakeSlice:根据 arena,创立新切片。
  • Clone:克隆一个 arena 的目标,并移动到内存堆上。

一些 arena 例子

以下案例和功能测验是根据 uptrace 在 Golang memory arenas [101 guide] 中共享的 arena 例子,本处进行引证,我就不自创一份了。

很适合在初学时作为 Demo 运用,计划也留着自己下次用时结合文档翻一番。

arena.NewArena

一起来快速入门。代码如下:

import "arena"
type T struct{
	Foo string
	Bar [16]byte
}
func processRequest(req *http.Request) {
	// 在函数最初创立一个 arena
	mem := arena.NewArena()
	// 在函数结束时开释 arena
	defer mem.Free()
	// 从请求的 arena 中请求一些目标
	for i := 0; i < 10; i++ {
		obj := arena.New[T](mem "T")
	}
	// 从请求的 arena 中请求切片目标(指定长度和容量)
	slice := arena.MakeSlice[T](mem, 100, 200 "T")
}

arena.Clone

如果要单独运用某个请求出来的目标。能够凭借 Clone 办法进行单独处理。

如下代码:

// 创立一个 arena
mem := arena.NewArena()
obj1 := arena.New[T](mem "T") // 分配一个 arena 目标
obj2 := arena.Clone(obj1) // 复制一个 arena 上的目标,移动到内存堆上
fmt.Println(obj2 == obj1) // 即使是根据复制出来的,两者并不彻底等价
// 开释 arena,obj1 不行运用,obj2 可正常运用
mem.Free()

开释了最早请求的 arena,Clone 办法在这里将会把 obj1 复制到新的内存堆上,再赋值给 obj2。后续要单独用 obj2 就能够持续运用。

reflect.ArenaNew

也能够结合 arena 和 reflect 两个规范库来进行运用。如下代码:

var typ = reflect.TypeOf((*T)(nil)).Elem()
mem := arena.NewArena()
defer mem.Free()
value := reflect.ArenaNew(mem, typ)
fmt.Println(value.Interface().(*T))

arena.MakeSlice

该办法的常规用法:

arena.MakeSlice[string](mem, length, capacity "string")

如果需要请求一个新切片并追加元素:

slice := arena.MakeSlice[string](mem, 0, 0 "string")
slice = append(slice, "")

需要留意的是,arena 现在不支撑 map。但你能够经过泛型来实现类似的效果。

arena.String

原则上 arena 不支撑 string。可是咱们依然能够经过 unsafe.String 办法的骚操作来变相实现。

如下代码:

src := "脑子进煎鱼了"
mem := arena.NewArena()
defer mem.Free()
bs := arena.MakeSlice[byte](mem, len(src "byte"), len(src))
copy(bs, src)
str := unsafe.String(&bs[0], len(bs))

在请求的 arena 开释后,该对应的 string 就无法运用了,需要特别留意。

功能体现

这个答应手艺办理内存的 arena 的特性是来源于内部,提案也是一路绿灯经过。(懂得懂)。

自述现已为 Google 许多应用节省了高达 15% 的 CPU 和内存运用量,主要原因是减少了废物收集 CPU 时间和堆内存运用量。

经过在 vmihailenco/golang-memory-arenas 项目中实践的功能对比。

没有用 arena:

/usr/bin/time go run arena_off.go
77.27user 1.28system 0:07.84elapsed 1001%CPU (0avgtext+0avgdata 532156maxresident)k
30064inputs+2728outputs (551major+292838minor)pagefaults 0swaps

运用了 arena:

GOEXPERIMENT=arenas /usr/bin/time go run arena_on.go
35.25user 5.71system 0:05.09elapsed 803%CPU (0avgtext+0avgdata 385424maxresident)k
48inputs+3320outputs (417major+63931minor)pagefaults 0swaps

运用了 arena 的代码运转速度更快,且运用的内存更少。

总结

Go 的各位大大们在功能优化中,不断地试图压榨 Go 的潜力。现在现已到了手艺办理内存的阶段了。

实践的测验结果来看,是有作用的。

有兴趣的小伙伴能够在 Go1.20 起就开端试用。不过需要留意,该特性由于发现了严重的 API 问题(想把 arena 应用到其他的规范库中,但这是个大事件),社区还需要认真思考后续的开展。现阶段处于处于阻滞状态。

从这次提案来看,真的是,内部需求一路猛如虎,直接冲上 master。外部需求就畏畏缩缩了。真双标?

引荐阅览

  • 醒醒吧,未来不会有 Go2 了!
  • Go1.20 那些事:PGO、编译速度、错误处理等新特性,你知道多少?
  • 向 Swift 学习?Go 考虑简单字符串插值特性