#垃圾回收 #指针 #gc #收集器 # #开销 #对象

nightly no-std zerogc

为Rust提供的零开销跟踪垃圾回收

11个版本

0.2.0-alpha.72022年2月14日
0.2.0-alpha.62021年12月20日
0.2.0-alpha.52021年8月18日
0.2.0-alpha.42021年7月10日
0.1.0 2020年6月22日

#305 in 内存管理

Download history 4/week @ 2024-04-14 5/week @ 2024-04-21 9/week @ 2024-04-28 4/week @ 2024-05-12 7/week @ 2024-05-19 9/week @ 2024-05-26 5/week @ 2024-06-02 6/week @ 2024-06-09 6/week @ 2024-06-16 8/week @ 2024-06-23 1/week @ 2024-06-30 12/week @ 2024-07-14 1/week @ 2024-07-21 164/week @ 2024-07-28

177 每月下载量
用于 4 crates

MIT 许可证

270KB
4.5K SLoC

ZeroGc

[WIP] 为Rust提供的零开销跟踪垃圾回收。

功能

  1. 易于使用,因为Gc<T>Copy并强制转换为引用。
  2. 修改指针时没有开销,因为Gc<T>Copy
  3. 实现无关的API
  4. 不安全代码可以完全自由地操作垃圾回收指针,且不需要理解区别
  5. 使用Rust的生命周期系统确保在显式安全点已知所有根,没有任何运行时开销。
  6. 收集只能通过显式safepoint调用发生,这些调用之间没有开销
  7. API支持移动对象(允许复制/代际GC)

它不要求编译器支持跟踪GC根(如Java/Go),而是使用阴影堆栈来跟踪GC根。收集只能在显式安全点发生

它使用Rust的生命周期系统确保在收集后不会访问释放的垃圾。分配的对象与垃圾回收器的生命周期相关联。安全点(可能的收集)被借用检查器视为突变。在没有zerogc(使用Vec或typed-arena)的情况下,这将使所有先前分配的指针无效。然而,提供给安全点的任何'根'都会重新绑定到新的生命周期(通过暗魔法)。

此API旨在实现无关性,仅定义了Trace特质和安全点的接口。

目前唯一的实现是zerogc-simple,它是一个基本的标记-清除收集器。它相对快速且轻量级,因此是一个不错的选择。它使用快速竞技场分配小型对象(可选;默认开启)并回退到系统分配器进行其他所有操作。

尽管标记/清除收集器很简单,但它相当快速,能够与Go/Java等生产级收集器相媲美。

这个库大部分没有文档,因为我认为它在未来的变化将会很大。请参见 二叉树 示例以获取一个基本样本。

状态

这极其实验性。它在使用Rust借用检查器的方式上,实际上是一片未知的领域。它看起来是可靠的,但我无法真正确信。

简单的标记/清除收集器通过了基本测试,但它仍然在内部依赖于大量的不安全代码。

最终我计划将其用于语言虚拟机,因此它需要非常灵活!我希望通过相同的API支持简单和复杂的收集器。

之前有一个复制收集器(它工作得很好)511be539228e7142,但由于内存使用量高,我已将其删除。

动机

最初我被rust gc 启发,想要创建一个安全的垃圾回收抽象,但我希望它没有运行时开销,并且指针是 Copy

rust-gc 解决的主要问题是找到垃圾回收的“根”。它的收集器使用运行时跟踪来维护每个GC对象的引用。

我熟悉一些JIT实现,我知道它们倾向于使用safepoint来显式控制垃圾回收可以发生的位置。这通常是一个不安全的操作。堆栈上的所有活动引用都必须在堆栈上给出或使用after。最终我意识到,我可以使用借用检查器来限制safepoint周围根的使用。我在一定程度上受到了indexing 使用生命周期来强制索引有效性的启发。

我们的收集器只在特定的safepoint处有运行时开销,即当更新影子栈时。

这不仅比跟踪每个指针的运行时跟踪或保守收集更快,而且更灵活。它为任何组合的代际、增量以及复制垃圾回收铺平了道路。

现有技术

自从原始的 rust gc 以来,其他人尝试创建零开销收集器。我在开始这个项目时并不了解这一点。

  1. shifgrethor
  • 这极其令人印象深刻。它似乎是唯一其他将 Gc: Copy 强制转换为引用的收集器。
  • 然而,收集器必须支持根的固定(我们做不到)
  • 请参阅此 博客文章系列 了解更多信息
  1. cell-gc
  • 不幸的是,它与Rust代码的接口相当笨拙,因此不合适。
  • 他们实现了一个基本的List VM作为概念验证。
  1. gc-arena
  • 这似乎是一个类似的想法,稍后实现。
  • 通过使用类似future的API来绕过生命周期的尴尬。
  • 与我们的收集器一样,safepoint是由用户显式请求的
  • 然而,他们不是尝试重新绑定生命周期,而是尝试使用future来构建状态机

缺点

  1. 垃圾收集器只能响应显式的 safepoint! 运行,而不是内存压力
    • 这是一个基本的设计限制。
    • 有人可能会认为这是一个特性,因为垃圾回收可以限制在特定的时间和地点。
    • 用户必须自由地插入safepoint
      • 通常,使用GC的高级语言会自动插入调用safepoint的代码
      • 它们的safepoint通常比我们的开销更低,因此需要权衡成本和效益
  2. 为类型实现 GcSafe 阻止它具有显式的 Drop 实现方式。
    • 这需要确保析构函数不会做坏事,因为我们不希望处理终结器。
    • 当然,不安全的代码不受此限制,因为它假定会正确行为(如果你知道你是安全的,你可以选择退出)。

实现缺陷

这些问题都不是基本设计缺陷。它们都可以被修复(并且应该被修复)。

  1. 目前,无法从使用safepoint的方法返回垃圾回收类型
    • 不是设计的根本限制。我计划修复API以支持这一点。
    • 不幸的是,这对于许多用例来说几乎是致命的,但可以通过使用'unchecked_safepoint'来解决这个问题
  2. 目前,无法在垃圾回收代码中使用短生命周期
    • 这是因为Gc<'gc, T>目前要求T: 'gc
    • 有可能放宽这个限制,但这可能会使使用变得更为繁琐...
  3. 对GC内存中可能别名向量的借用限制
    • 由于可能共享的GC内存,很难执行Rust的可变性规则(参见问题#30)
  4. 实现还不是代际的(目前不是)
  5. 由于API尚未充分利用泛型关联类型,从泛型上下文使用时比较繁琐

依赖

~3.5–4.5MB
~84K SLoC