Immunant 工程师将 VideoLAN 和 FFmpeg 的 AV1 解码器从 C 移植到 Rust
在由两篇文章组成的系列中,Immunant 的软件工程师 Stephen Crane 和 Khyber Sen 详细描述了他们如何为互联网安全研究组(ISRG)将 VideoLAN 和 FFmpeg 的 AV1 解码器从 C 语言移植到 Rust。文章涵盖了确保不破坏功能以及优化性能的诸多细节。
AV1 解码器背景
VideoLan VLC 和 FFMpeg 中使用的 AV1 解码器 dav1d 已经开发了六年多,包含约 5 万行 C 代码和 25 万行汇编代码。该解码器成熟、快速且广泛使用,并且高度优化,具有小巧、可移植和极速的特点。因此,工程师们选择将其移植到 Rust,而不是从头重写。
移植策略
工程师们首先需要决定是逐步移植还是使用 c2rust 工具将整个代码库转换为 Rust,然后再进行重构和重写以确保安全性和符合 Rust 习惯。他们选择了 c2rust 方法,主要原因有两点:一是可以在重构过程中测试移植的代码,二是减少对领域专家知识的依赖。
重构挑战
将移植后的 Rust 代码重构为安全且符合 Rust 习惯的代码面临多项挑战,包括 C 和 Rust 在生命周期管理、内存所有权、缓冲区指针和联合体等方面的不匹配,以及 dav1d 设计中强烈依赖跨线程的共享可变访问。
线程安全问题
通过使用 Mutex 和 RwLock 锁,并验证线程在运行时可以访问数据而不会引入延迟,解决了与共享状态相关的线程安全问题。对于多线程并发访问单个缓冲区的情况,工程师们创建了一个缓冲区包装类型 DisjointMut,负责处理可变借用并确保每个借用具有独占访问权。
其他挑战
自引用结构和无标签联合体也是挑战之一。由于 Rust 不允许自引用结构,光标指针被替换为整数索引,而上下文结构则通过函数参数引用。无标签联合体在方便时转换为带标签的 Rust 联合体,而在其他情况下,使用 zerocopy 库在运行时将相同字节重新解释为两种不同类型,以避免改变联合体表示和大小。
性能优化
移植的主要目标之一是保持性能,因此工程师们在重构阶段密切监控每个提交的性能回归。在过渡到安全代码的过程中,他们发现性能主要受到动态调度、边界检查和结构初始化等微妙因素的影响。最终,他们处理了与分支、内联和堆栈使用相关的更精细优化。
结果与总结
性能优化工作显著减少了移植引入的开销,从 11% 降至 6%。整个移植过程耗时超过 20 人月,由三名开发人员完成,比最初预期的需要更多手动努力。Crane 表示,这项研究表明可以将现有的 C 代码重写为安全且高性能的 Rust 代码,并解决所有线程和借用挑战。
对于安全性至关重要的应用,rav1d 提供了无需额外开销(如沙盒)的内存安全实现。工程师们相信,随着持续优化和改进,Rust 实现可以在所有情况下与 C 实现竞争,同时提供内存安全性。
更多关于 rav1d 创建过程的详细信息,请参阅原文。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。