前言

作为一个 Gopher,想水这边文章已经好久了。

关于 Arena 的 API 我就不赘述了,没几个接口,翻一秒代码就了解完了:

本文就专注于分享一下我总结的 Arena 包的设计,前因后果,注意事项,以及未来的应用场景。

如有错误,欢迎评论区指正。

Arena 是怎么做到手动管理内存的?

如果要在 GC 的语言里面实现 C++ / Rust 那样的内存管理方式,会过于复杂,因为这要求 GC 算法必须很细心的兼容手动管理的内存段。

Arena 采用的方案是 Region-based memory management,具体流程如下:

Arena 管理内存
  1. 开发者手动申请内存块。
  2. 开发者在这个内存块中不断申请新的内存空间,期间系统不进行回收。
  3. 开发者调用 API 释放整个内存块,然后系统进行内存回收。

受限于设计 Arena 无法适用于所有的场景,但已经能解决足够多的问题了。

如,对于常见的后端,可以通过如下手动管理内存的方式,流程优化性能:

通过 Arena 优化性能
  1. 当一个用户请求过来后,Http 框架会申请 Arena 内存空间。
  2. 处理请求期间程序所有产生的对象都会通过 Arena 创建。
  3. 在 Http 返回结束后,框架整块释放 Arena。

不会产生副作用的场景,都可以放心用 Arena。

为什么需要手动管理内存?

核心动力要追溯到 Golang 本身的应用场景。

Golang 常应用于一些性能敏感的服务如:云原生网关,RPC 框架,区块链,甚至很多数据库。

这些服务基本处于系统底层,一个性能的抖动会被放大数倍。

不巧,Golang 是自带 GC 的语言,GC 的不确定性对系统的稳定性始终是个隐患。

社区也没闲着,强壮的大佬们还是自食其力地去克服 GC 带来的困难,如 TIDB 就自己实现了一个 Arena:

总之,应用场景摆在这里,手动管理内存的能力引入是迟早的。

使用 Arena 要注意什么?

这一块内容主要就是对 Github Proposal 讨论内容的一个翻译和搬运了,有兴趣的可以看原 Proposal:

总之,使用 Arena 一定要注意它的生命周期。

例如在 Arena 中申请对象后,开发者要妥善处理这个对象,特别是需要将这个对象存储到缓存中时。

由于缓存的生命周期时常超越 Arena 的生命周期,一个不留神可能就导致,程序访问了一段已经被释放的内存。如下所示:

错误的使用方式

为了避免类似问题,需要程序员们小心翼翼地使用 Arena 包的 Clone API,在对象的生命周期大于 Arena 的生命周期时,把对象升级到 Heap 上。

小心,小心,再小心。

未来的应用场景

未来一定不是每一位 Gopher 都需要使用 Arena 的。

Arena 主要的应用场景可能如下:

  1. 在 ORM 框架,JSON 序列化框架,或者 RPC 序列化框架中引入。通过手动管理内存大幅提高性能。
    据说 Google 内部已经应用了 Arena,并且获得了不错的性能收益。
  2. 云原生网关等底层组件进行性能升级,各大公司内部又是一波满满的 KPI 啊。
  3. 推出更多 Go 编写的中间件。如 Go 编写的 Reids 平替?可能性无限。

对于普通 Gopher 开发者,我认为大家最好的做法就是:不要在业务项目里面自己用 Arena。

大家只需要静等各个流行的类库进行一波升级后,直接坐地获得满满的性能收益。

这一波更新,大佬们获得了 KPI,我这种菜鸟坐吃收益,Gopher 们都赢麻了。