NiQin (blog: 泥芹) shared the aphorism --
I returned and saw under the sun, that the race is not to the swift, nor the battle to the strong, neither yet bread to the wise, nor yet riches to men of understanding, nor yet favour to men of skill; but time and chance happeneth to them all. -- 《圣经》

[Rust] basedrop:Rust 生态中,适用于实时音频的垃圾收集器

💥 内容涉及著作权,均归属作者本人。若非作者注明,默认欢迎转载:请注明出处,及相关链接。

Summary: 在实时音频中,截止时间至关重要。您的代码仅有几毫秒的时间来填充一个缓冲区,其中的样本将被发送到 DAC。但是,这几毫秒,也可能要与许多其他音频处理程序共享。如果您的代码花费太长时间来生成这些样本,那么就没有第二次机会;音频根本不会被播放,用户会听到一个令人讨厌的小故障,或者被口吃的声音代替。 为了防止这种情况,实时音频代码必须避免执行任何操作,这些操作可能会在无限或不可预测的时间内阻塞音频线程。如何在受上述限制的情况下,以可管理和高效的方式完成这一任务?Basedrop 是我试图为这个问题提供的一个解决方案。

Topics: rust 实时音频 real-time-audio

首先对关心笔者的朋友表示感谢。上周是因事外出,所以未有更新。

因个人开发需要音频处理,笔者在搜索相关工具时,发现了一个很新的实时音频 crate:basedrop,目前 github 星星数 20 左右。在对 basedrop 浅显实践后,感觉此 crate 非常棒,因此分享。

示例代码为笔者实践而仓促编写,参考了 docs.rs 站点的 basedrop 文档;后来发现作者有介绍的文章,所以文字内容摘译自 glowcoil 于 2021-04-26 发表的文章 basedrop: a garbage collector for real-time audio in rust

在实时音频中,截止时间至关重要。您的代码仅有几毫秒的时间来填充一个缓冲区,其中的样本将被发送到 DAC(译注:数字模拟转换器,Digital to analog converter,是将输入的数字信号转换成具有模拟位准的信号)。但是,这几毫秒,也可能要与许多其他音频处理程序共享。如果您的代码花费太长时间来生成这些样本,那么就没有第二次机会;音频根本不会被播放,用户会听到一个令人讨厌的小故障,或者被口吃的声音代替。

为了防止这种情况,实时音频代码必须避免执行任何操作,这些操作可能会在无限或不可预测的时间内阻塞音频线程。这些操作包括:文件和网络 I/O、内存分配和释放,以及使用锁与非音频线程同步等,因为这些操作的“实时安全”性不被认可。相反,像 I/O 和内存分配这样的操作应该在其它线程上执行。而线程的同步操作,应该使用对音频线程没有等待的原语来执行。Ross Bencina 的经典博客文章《时间不等人(Time Waits for Nothing)》中,更全面地概述了这一主题。

考虑到音频软件通常需要分配内存,并从音频线程中使用内存。那么问题就来了:如何在受上述限制的情况下,以可管理和高效的方式完成这一任务?Basedrop 是我试图为这个问题提供的一个解决方案。

延迟回收

考虑一个简单的场景:我们有一个存储在 Vec<f32> 中的样本缓冲区,可能是从磁盘合成或加载的,我们希望在音频线程使用它。作为解决方案的初始草图,我们可以使用无等待(wait-free)且有界容量(bounded-capacity)的 SPSC 通道(译注:高性能无锁队列,比如 rtrb crate),以将缓冲区发送到音频线程。然后,当我们使用完它并希望回收内存时,我们可以通过另一个 SPSC 通道将其发送回非实时线程,以进行释放。

在较简单的情况下,此解决方案效果良好。但是,随着应用程序复杂性的增加,它也有缺点。例如,如果在音频线程之间传输大量分配,则用于返回分配的固定容量通道,则可能会被填满。由于在这种情况下阻止音频线程是不可接受的,因此应用程序需要确保信道的轮询频率足以适配,并且信道总是可满足最坏情况时需要的容量(使用更复杂的动态分配设计)。或者,如果当前无法返回分配,音频线程可以继续播放,但不出错。此外,这个解决方案依赖于程序员的纪律性,以确保分配总是被释放。而 Rust 的 RAII(译注:资源获取即初始化,详细请参阅 Rust 所有权的内存与分配)设计,对于这方面的错误,在很大程度上是不可见的。assert_no_alloc 等诊断工具 crate,可以在很大程度上检测此类错误,在编译时有一个保证是很好的。

Basedrop 的解决方案是使用 MPSC 链表队列,替换用于返回分配的固定容量的环形缓冲区。在分配时,为任何要与音频线程共享的内存块创建 MPSC 链表队列节点,并内联存储。当音频线程准备释放一段内存以进行回收时,可以通过无分配、无等待的操作将相应的节点推送到队列中。此模式由一对智能指针封装:Owned<T>Shared<T>,类似于 Box<T>Arc<T>,它们将内容推送到队列中,进行延迟回收,而不是直接丢弃。然后可以使用 basedrop 的 Collector 类型,在另一个线程上定期处理队列。

此系统的优点是回收通道不可能变满,缺少完全打开的 OOM(译注:OutOfMemory)。也不可能忘记将要收集的东西返回,只要它最初是用 Owned<T> 或者 Shared<T> 封装的。特别是 Shared<T>,其为在音频和非音频线程之间共享不可变和持久的数据结构,提供了令人兴奋的可能性。这种方式对于手动的消息传递方法来说,是很麻烦或不可能的。

Collector 的使用

丢弃队列中的所有垃圾

use basedrop::{Collector, Handle, Owned};
use core::mem::drop;

let mut collector = Collector::new();
let handle = collector.handle();
let x = Owned::new(&handle, 1);
let y = Owned::new(&handle, 2);
let z = Owned::new(&handle, 3);

assert_eq!(collector.alloc_count(), 3);

drop(x);
drop(y);
drop(z);
collector.collect();

assert_eq!(collector.alloc_count(), 0);

尝试删除队列中的第一个分配

如果成功,返回 true;否则,返回 false

use basedrop::{Collector, Handle, Owned};
use core::mem::drop;

let mut collector = Collector::new();
let handle = collector.handle();
let x = Owned::new(&handle, 1);
let y = Owned::new(&handle, 2);
let z = Owned::new(&handle, 3);

assert_eq!(collector.alloc_count(), 3);

drop(x);
drop(y);
drop(z);

assert!(collector.collect_one());
assert!(collector.collect_one());
assert!(collector.collect_one());

assert!(!collector.collect_one());
assert_eq!(collector.alloc_count(), 0);

SharedCell

Basedrop 还提供了另一个与音频线程共享内存的原语,称为 SharedCell<T>SharedCell<T> 充当一个线程安全的可变内存位置,用于存储 Shared<T> 指针,提供 getsetreplace 方法(与 Cell 非常类似),用来获取和更新内容。我的设想,这将被用作一种非实时线程,以原子方式发布数据的方法。然后,实时音频线程可以不可变地观察到这些数据。

以无锁方式实现此模式,其主要困难在于获取引用计数指针的副本。实际上包括两个步骤:首先,获取实际指针;然后,增加引用计数。在这两个步骤之间,决不能允许写入器用新值替换指针,将前一个值的引用计数减为零,然后释放其引用,因为这将导致读取器在释放后使用。对于这个问题有各种可能的解决方案,有不同的权衡。

SharedCell<T> 采用的方法是在存储的指针旁边,保留一个读取器计数。读取器在获取指针时,递增此计数,只有在成功递增指针的引用计数后,才能递减。反过来,在替换存储的指针之后,写入程序会循环,直到观察到计数为零,然后才允许它们移动(Rust 中的 move),并可能减少引用计数。此方案可被设计成低成本、无阻塞的读取器,而写入器的开销要高一些。我认为这是实时音频的适当折衷,读取器(音频线程)的延迟期限要短得多,执行频率也要比写入器高得多。

SharedCell 的使用

替换包含的 Shared<T>,减少进程中的引用计数

use basedrop::{Collector, Shared, SharedCell};

let collector = Collector::new();
let x = Shared::new(&collector.handle(), 3);
let cell = SharedCell::new(x);

let y = Shared::new(&collector.handle(), 4);
cell.set(y);

替换包含的 Shared<T> 并返回

use basedrop::{Collector, Shared, SharedCell};

let collector = Collector::new();
let x = Shared::new(&collector.handle(), 3);
let cell = SharedCell::new(x);

let y = Shared::new(&collector.handle(), 4);
let x = cell.replace(y);

释放 SharedCell,并返回包含的 Shared<T>

这样做是安全的,因为我们保证是 SharedCell 的唯一持有者。

use basedrop::{Collector, Shared, SharedCell};

let collector = Collector::new();
let x = Shared::new(&collector.handle(), 3);
let cell = SharedCell::new(x);

let x = cell.into_inner();

未来展望

BaseTrop 当前不支持动态类型,如 Owned<[T]> 或者 Owned<dyn Trait>。等待 Rust 的 CoerceUnsized 或者 equivalent 稳定时,这一点应该可以实现。目前,动态类型可以通过将 DST 封装到另一层分配中来解决,没有太多问题。

此外,Shared<T> 当前不支持循环数据结构的弱引用,如 Arc<T> 所做的那样。这会使引用计数逻辑复杂化(参见 Arc 源代码),我想从一些简单的东西开始。

我还想探索比引用计数开销更小的内存回收策略,例如 Linux 内核中的 RCU 模式基于代(epoch-based)的回收,以及基于静态(quiescent state-based)的回收。我还没有想到这样一种设计,既符合 Rust 的所有权,又满足实时音频(和音频插件)的限制,但我认为这是有希望的方向。

谢谢您的阅读!


Related Articles

  1. [Rust] RustHub.org:基于 Rust-Web 技术栈,及 image-rs、fluent-rs、rhai-script ……
  2. [WebAssembly] yew SSR 服务器端渲染
  3. [Rust] async-std 创建者对于最近“项目是否已死?”,移除对其支持等的答复
  4. [Rust] Rust 1.56.0 版本和 Rust 2021 版次发布,新特性一览,及项目的迁移、升级
  5. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 博客应用的体验报告
  6. [Rust] Rust 官方周报 399 期(2021-07-14)
  7. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(5)- 构建 HTTP 请求、与外部服务器通信的两种方法
  8. [Rust] Rust 官方周报 398 期(2021-07-07)
  9. [Rust] Rust 官方周报 397 期(2021-06-30)
  10. [Rust] Rust 官方周报 396 期(2021-06-23)
  11. [Rust] Rust 官方周报 395 期(2021-06-16)
  12. [Rust] Rust 1.53.0 明日发布,关键新特性一瞥
  13. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(3)- rhai 脚本、静态/资源文件、环境变量等
  14. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(2)- 获取并解析 GraphQL 数据
  15. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(1)- crate 选择及环境搭建
  16. [Rust] Rust 官方周报 394 期(2021-06-09)
  17. [Rust] Rust web 前端库/框架评测,以及和 js 前端库/框架的比较
  18. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(4)- 获取 GraphQL 数据并解析
  19. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(3)- 资源文件及小重构
  20. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(2)- 组件和路由
  21. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(1)- 起步及 crate 选择
  22. [Rust] Rust 官方周报 393 期(2021-06-02)
  23. [Rust] Rust 官方周报 392 期(2021-05-26)
  24. [Rust] Rust 中,对网址进行异步快照,并添加水印效果的实践
  25. [Rust] Rust 官方周报 391 期(2021-05-19)
  26. [Rust] Rust,风雨六载,砥砺奋进
  27. [Rust] 为什么我们应当将 Rust 用于嵌入式开发?
  28. [Rust] Rust 官方周报 390 期(2021-05-12)
  29. [Rust] Rust + Android 的集成开发设计
  30. [Rust] Rust 1.52.1 已正式发布,及其新特性详述
  31. [Rust] 让我们用 Rust 重写那些伟大的软件吧
  32. [Rust] Rust 1.52.0 已正式发布,及其新特性详述
  33. [Rust] Rust 官方周报 389 期(2021-05-05)
  34. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(4) - 变更服务,以及小重构
  35. [Rust] Rust 1.52.0 稳定版预发布测试中,关键新特性一瞥
  36. [Rust] Rust 生态中,最不知名的贡献者和轶事
  37. [Rust] Rust 基金会迎来新的白金会员:Facebook
  38. [Rust] Rustup 1.24.1 已官宣发布,及其新特性详述
  39. [Rust] Rust 官方周报 388 期(2021-04-28)
  40. [Rust] Rust 官方周报 387 期(2021-04-21)
  41. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(4)- 变更服务,以及第二次重构
  42. [Rust] Rustup 1.24.0 已官宣发布,及其新特性详述
  43. [Rust] basedrop:Rust 生态中,适用于实时音频的垃圾收集器
  44. [Rust] Rust 编译器团队对成员 Aaron Hill 的祝贺
  45. [Rust] Jacob Hoffman-Andrews 加入 Rustdoc 团队
  46. [机器人] 为什么应将 Rust 引入机器人平台?以及机器人平台的 Rust 资源推荐
  47. [Rust] rust-lang.org、crates.io,以及 docs.rs 的管理,已由 Mozilla 转移到 Rust 基金会
  48. [Rust] Rust 官方周报 386 期(2021-04-14)
  49. [Rust] Rust 编译器(Compiler)团队 4 月份计划 - Rust Compiler April Steering Cycle
  50. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(3) - 重构
  51. [Rust] 头脑风暴进行中:Async Rust 的未来熠熠生辉
  52. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(2) - 查询服务
  53. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务 - 起步及 crate 选择
  54. [Rust] Rust 2021 版本特性预览,以及工作计划
  55. [Rust] Rust 用在生产环境的 42 家公司
  56. [Rust] 构建最精简的 Rust Docker 镜像
  57. [Rust] Rust 官方周报 385 期(2021-04-07)
  58. [Rust] 使用 Rust 做异步数据采集的实践
  59. [Rust] Android 支持 Rust 编程语言,以避免内存缺陷
  60. [Rust] Android 平台基础支持转向 Rust
  61. [Rust] Android 团队宣布 Android 开源项目(AOSP),已支持 Rust 语言来开发 Android 系统本身
  62. [Rust] RustyHermit——基于 Rust 实现的下一代容器 Unikernel
  63. [Rust] Rustic:完善的纯粹 Rust 技术栈实现的国际象棋引擎,多平台支持(甚至包括嵌入式设备树莓派 Raspberry Pi、Buster)
  64. [Rust] Rust 迭代器(Iterator trait )的要诀和技巧
  65. [Rust] 使用 Rust 极致提升 Python 性能:图表和绘图提升 24 倍,数据计算提升 10 倍
  66. [Rust] 【2021-04-03】Rust 核心团队人员变动
  67. [Rust] Rust web 框架现状【2021 年 1 季度】
  68. [Rust] Rust 官方周报 384 期(2021-03-31)
  69. [Rust] Rust 中的解析器组合因子(parser combinators)
  70. [生活] 毕马威(KPMG)调查报告:人工智能的实际采用,在新冠疫情(COVID-19)期间大幅提升
  71. [Python] HPy - 为 Python 扩展提供更优秀的 C API
  72. [Rust] 2021 年,学习 Rust 的网络资源推荐(2)
  73. [Rust] 2021 年,学习 Rust 的网络资源推荐
  74. [生活] 况属高风晚,山山黄叶飞——彭州葛仙山露营随笔
  75. [Rust] Rust 1.51.0 已正式发布,及其新特性详述
  76. [Rust] 为 Async Rust 构建共享的愿景文档—— Rust 社区的讲“故事”,可获奖
  77. [Rust] Rust 纪元第 382 周最佳 crate:ibig 的实践,以及和 num crate 的比较
  78. [Rust] Rust 1.51.0 稳定版本改进介绍
  79. [Rust] Rust 中将 markdown 渲染为 html
  80. [生活] 国民应用 App 的用户隐私数据窥探
  81. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(3)- 重构
  82. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(2)- 查询服务
  83. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(1)- 起步及 crate 选择
  84. [Rust] Rust 操控大疆可编程 tello 无人机

Topics

rust(84)

graphql(17)

rust-官方周报(17)

webassembly(16)

wasm(10)

tide(9)

async-graphql(9)

yew(9)

rust-web(8)

rust-官方博客(8)

this-week-in-rust(6)

mysql(5)

actix-web(5)

rbatis(5)

android(4)

mongodb(3)

json-web-token(3)

jwt(3)

cargo(3)

技术延伸(3)

rust-wasm(3)

trunk(3)

handlebars(3)

rhai(3)

async-std(3)

用户隐私(2)

学习资料(2)

python(2)

ai(2)

人工智能(2)

postgresql(2)

rust-compiler(2)

rust-基金会(2)

rust-foundation(2)

rustup(2)

rust-toolchain(2)

rust-工具链(2)

rust-游戏开发(2)

rust-区块链(2)

rust-2021(2)

graphql-client(2)

surf(2)

rust-game(2)

rusthub(2)

tello(1)

drone(1)

无人机(1)

隐私数据(1)

markdown(1)

html(1)

crate(1)

async(1)

异步(1)

旅游(1)

不忘生活(1)

葛仙山(1)

hpy(1)

python-扩展(1)

正则表达式(1)

解析器组合因子(1)

组合器(1)

regular-expression(1)

parser-combinator(1)

regex(1)

官方更新(1)

rust-工作招聘(1)

rust-技术资料(1)

rust-周最佳-crate(1)

rust-web-框架(1)

rust-web-framework(1)

rust-核心团队(1)

rust-core-team(1)

rust-language-team(1)

pyo3(1)

rust-python-集成(1)

python-性能改进(1)

迭代器(1)

iterator-trait(1)

国际象棋(1)

chess(1)

游戏引擎(1)

game-engine(1)

虚拟化(1)

unikernel(1)

rustyhermit(1)

linux(1)

virtualization(1)

sandboxing(1)

沙箱技术(1)

数据采集(1)

异步数据采集(1)

docker(1)

镜像(1)

生产环境(1)

rust-评价(1)

rust-2021-edition(1)

rust-2021-版本(1)

graphql-查询(1)

vision-doc(1)

愿景文档(1)

代码重构(1)

steering-cycle(1)

方向周期(1)

隐私声明(1)

机器人(1)

robotics(1)

rustdoc(1)

rust-编译器(1)

实时音频(1)

real-time-audio(1)

变更服务(1)

mutation(1)

查询服务(1)

query(1)

rust-贡献者(1)

rust-轶事(1)

rust-稳定版(1)

rust-预发布(1)

rust-测试(1)

安全编程(1)

可信计算(1)

安全代码(1)

secure-code(1)

rust-android-integrate(1)

rust-embedded(1)

rust-嵌入式(1)

rust-生产环境(1)

rust-production(1)

网页快照(1)

网页截图(1)

水印效果(1)

图片水印(1)

yew-router(1)

css(1)

web-前端(1)

wasm-bindgen(1)

区块链(1)

blockchain(1)

dotenv(1)

标识符(1)

rust-1.53.0(1)

rust-1.56.0(1)

rust-项目升级(1)

异步运行时(1)

ssr(1)

tokio(1)

warp(1)

reqwest(1)

graphql-rust(1)


Elsewhere

- Open Source
  1. github/zzy
  2. github/sansx
- Learning & Studying
  1. Rust 学习资料 - 泥芹