8个版本 (4个破坏性)

使用旧的Rust 2015

0.5.0 2017年8月12日
0.4.2 2017年8月5日
0.3.0 2017年8月4日
0.2.3 2017年8月2日
0.1.2 2017年5月26日

#408 in 内存管理

每月下载 23

MIT 许可证

105KB
1.5K SLoC

conc — 一个高效的并发回收系统

conc 基于“危险指针”来创建一个极高性能的系统,用于并发处理内存。它是一个通用、方便、内存轻量级(有时更快)的基于纪元的回收(参见权衡部分)的替代品。

概述

  • 高级API
    • Atomic<T> 用于无锁的读写容器。
    • sync 通过 conc 实现的基本数据结构。
      • Treiber<T> 用于并发栈。
      • Stm<T> 用于STM的简单实现。
  • 低级API
    • add_garbage() 用于排队垃圾销毁。
    • Guard<T> 用于阻塞销毁。
  • 运行时控制
    • gc() 用于收集垃圾以减少内存。
    • settings 用于在运行时重新配置系统。

为什么?

aturon的博客文章很好地解释了并发内存处理的问题,尽管它基于基于纪元的回收,而这正是此软件包的替代品。

核心内容基本上是,在大多数并发数据结构中,你需要删除对象(否则会出现内存泄漏),但是无法安全地这样做,因为没有方法知道是否有其他线程正在访问该对象。这(以及其他回收系统)为这个问题提供了一个解决方案。

使用方法

虽然提供了低级API,但通常可以使用 conc::Atomic<T> 抽象。这很像熟悉的Rust API。它允许程序员通过引用并发访问一个值,并更新它等。有关更多信息,请参阅相应的文档。

如果您想使用 conc 实现自己的结构,您必须学习如何使用 Guard<T>add_garbage。简而言之,

  • conc::add_garbage() 添加一个带有指针的析构函数,该析构函数将在没有人再读取数据时最终运行。换句话说,它作为并发版本的 Drop::drop()
  • Guard<T> “保护”一个指针不被销毁。也就是说,它推迟销毁(由 conc::add_garbage() 计划),直到守卫消失。

有关使用和行为详情,请参阅相应的API文档。

调试

启用功能 debug-tools 并设置环境变量 CONC_DEBUG_MODE。例如,CONC_DEBUG_MODE=1 cargo test --features debug-tools。要获取每个消息后的堆栈跟踪,请设置环境变量 CONC_DEBUG_STACKTRACE

示例

请参阅 sync 源代码

权衡 - 为什么不是 crossbeam/epochs?

Epochs(EBR)通常比这个crate快,然而这个crate与epochs相比的主要优势是,在极端情况下,它不会出现内存爆炸。

大多数其他更快解决方案的问题在于,如果有大量线程(比如16个)不断读取/存储一些指针,它将永远无法达到可以回收的状态。

换句话说,给定足够的线程数量和频率,回收之间的间隔可能会非常非常长,导致非常高的内存使用,并可能导致OOM崩溃。

这些问题并非假设。在我测试TFS的缓存系统时发生在我身上。本质上,即将被销毁的垃圾积累了几个GB,而从未进入回收周期。

这让人想起MongoDB的争论。它可能是最快的解决方案¹,但如果它甚至不能在这些情况下确保一致性,那么它的意义何在?

话虽如此,在其他情况下,其他库可能非常合适(例如,如果您有有限数量的线程和中等长度的访问间隔),并且也更快。

¹如果您想要一个超级快的内存回收系统,请尝试NOP™,不要调用析构函数。

其他优点

  • 由于设计上的考虑,《conc》API在接口方面更为轻量,因为它不需要调用方固定epochs或类似对象。当你设计更复杂的数据结构时,这一点尤其方便。
  • 《conc》对象(Guard<T>)不依赖于生命周期或类似概念,这意味着它与future-rs等其他系统兼容。
  • 在《conc》中,当有活动对象时,线程可以导出垃圾,这意味着内存不会在非停止使用的情况下积累。
  • 《conc》可以通过settings模块进行运行时配置。
  • 更好的调试工具。
  • 更全面测试和文档。
  • 对运行时具有更精细的控制。
  • 访问低级API。

缺点

  • 在许多情况下,它的速度较慢。
  • 预实现的数据结构较少(目前)。

设计与内部结构

它基于危害指针,尽管有一些差异。基本思想是系统跟踪一定数量的“危害”。只要危害保护某些对象,该对象就不能被删除。

偶尔,一个线程会通过扫描危害并找到当前没有任何危害保护的对象来执行垃圾回收。然后,这些对象被删除。

为了提高性能,我们采用分层方法:垃圾(最终将被删除的对象)和危害都缓存于线程本地。这减少了原子操作和缓存缺失的数量。

有关更多详细信息,请参阅这篇博客文章

垃圾回收

并发管理的对象的垃圾回收是在每次n次释放之间自动进行的,其中n来自某个概率分布。

请注意,垃圾回收周期可能无法清除所有对象。例如,某些对象可能被危害保护。其他对象可能尚未从线程本地缓存中导出。

性能

值得注意的是,通过此库进行原子读取通常需要三个原子CPU指令,这意味着如果您正在遍历列表或类似内容,这个库可能不适合您。

设置

您可以通过settings模块随时重新配置系统。

还有一些预设。例如,如果您遇到高内存使用情况,您可以执行

conc::settings::set_local(conc::settings::Settings::low_memory());

依赖关系

~0.5–1.2MB
~20K SLoC