写点什么

从 C 迁移到 Rust 的挑战与经验教训

作者:Sergio De Simone

  • 2024-11-18
    北京
  • 本文字数:1595 字

    阅读完需:约 5 分钟

从 C 迁移到 Rust 的挑战与经验教训

在一个系列文章中,Immunant 软件工程师 Stephen Crane 和 Khyber Sen 讲述了他们如何将互联网安全研究小组 (ISRG) 的 VideoLAN 和 FFmpeg AV1 解码器从 C 语言移植到 Rust 语言。该系列文章详细介绍了他们如何确保不出错并优化性能。


VideoLan VLC 和 FFMpeg 中使用的 AV1 解码器 dav1d 已经开发了六年多,包含大约 5 万行 C 代码和 25 万行汇编程序。正如 Crane 所说的那样,它成熟、速度快且应用广泛。因为代码高度优化,所以它的体积小、可移植性好、速度快。因此,他们坚持要移植,而不是使用 Rust 从头开始重写。


Immunant 的工程师们首先要做的选择是,是一步一步地进行移植,还是使用 c2rust 移植整个代码库,获得一个不安全但可运行的 Rust 实现,然后再以此为基础进行重构和重写,使其变得安全而又符合 Rust 的语言习惯。最终,他们决定采用 c2rust,因为它有两大优势:一是可以在重构的同时测试移植的代码,二是降低了对专家领域知识的要求。


我们发现,在重写和改进 Rust 代码的过程中,从一开始就进行全面的 CI 测试是非常有益的。我们可以对代码库进行横向修改,并在每次提交时运行已有的 dav1d 测试。[…] 该项目的大部分团队成员都是系统编程和 Rust 方面的专家,但之前并没有 AV 编解码器方面的经验。我们的编解码器专家 Frank Bossen 为项目提供了宝贵的指导,但大部分的工作他并不需直接要参。


将移植生成的 Rust 代码重构为安全、符合语言习惯的 Rust 代码面临着许多挑战,其中一些挑战与 C 和 Rust 之间的不匹配有关,例如生命周期管理(借用)、内存所有权、缓冲指针和联合体;另一些挑战则源于 dav1d 的设计,它非常依赖于对跨线程共享可变数据的访问。


通过使用MutexRwLock加锁,并在运行时使用Mutex::try_lock()RwLock::try_read()/RwLock:: try_write()进行验证,他们确保了线程可以访问数据而且又不会引入延迟,从而解决了与共享状态相关的线程安全问题。


这种方法可以很好地处理只有一个线程需要修改跨线程共享值的情况。然而,dav1d 还依赖于多个线程对单个缓冲区的并发访问,其中每个线程访问缓冲区的特定子区域。对此,Immunant 工程师并没有使用更符合 Rust 语言习惯的方法,即使用专门分配给不同线程的不相连的区域,而是创建了一个缓冲区封装类型DisjointMut,负责处理可变借用,并确保每一个都能独占访问。


另外两个颇具挑战性的领域是自引用结构(主要用于跟踪缓冲区位置的游标以及上下文结构之间的链接)和无标签联合体。由于 Rust 不允许使用自引用结构,所以游标指针被整数索引取代,而上下文结构之间的链接被取消,并通过函数参数进行引用。在适当的时候,无标签联合体会被转换为带标签的 Rust 联合体,而在其他情况下,zerocopy crate 会在运行时将相同的字节重新解释为两种不同的类型,以避免改变联合体的表示和大小。


移植的一个主要目标是保持性能不变。因此,Immunant 的工程师在每次提交的重构阶段都会仔细监控性能回归情况。在向安全代码转换的过程中,他们意识到,性能主要是受到一些微妙因素的影响,如动态分派汇编代码、边界检查和结构初始化的成本。最后,他们进行了与分支、内联和堆栈使用相关的更细致的优化。


性能优化工作显著降低了移植带来的开销,从 11% 降至 6%。按照 Crane 的说法,总体上,将 dav1d 移植到 rav1d 花费了三个开发人员 20 多个月的时间,所耗费的人工比最初预计的要多。但这也表明,将现有的 C 代码重写为安全、高性能的 Rust 代码并解决所有线程和借用难题是可能的。


对特别注重安全性的应用程序,rav1d 提供了一个内存安全的实现,而且不会因为沙箱等风险缓解措施而额外增加开支。我们相信,通过不断地优化和改进,在任何情况下,Rust 实现都可以与 C 语言实现相媲美,同时还能提供内存安全性。


他们从 rav1d 的诞生过程中学到的东西远不止这些,如果想了解更多信息,请阅读原文。


查看原文链接:

https://www.infoq.com/news/2024/10/porting-av1-decoder-rust/

2024-11-18 08:106874

评论

发布
暂无评论
发现更多内容

DDD独立类模式你用到了吗

神帅

DDD 独立类模式

Apache APISIX 2.12.0 版本发布,新功能更适配新一年!

API7.ai 技术团队

开源 后端

微信业务架构图

Geek_36cc7c

什么时候该减少质量投入?

QualityFocus

质量管理 软件测试 测试思维

模板

Anthony

模块六作业

novoer

「架构实战营」

领域划分的规则是什么

神帅

DDD 领域划分规则

企业管理中用户数据同步机制解决思路

全象云低代码

低代码 数据同步 用户数据 后端技术

云厂商第一家,腾讯安全获国家级信息安全服务资质“双认证”!

腾讯安全云鼎实验室

云安全 安全服务

LabVIEW实现PCB电路板元器件匹配定位(实战篇—7)

不脱发的程序猿

计算机视觉 图像处理 LabVIEW PCB电路板元器件匹配定位

架构训练营模块一作业

苍狼

学生管理系统架构设计方案

Geek_36cc7c

数据治理平台化的通用框架设计

小鲸数据

架构图 - 微信 & 学生管理系统

Ntropy

架构实战营

三峡集团研究院:基于物联网的大规模储能系统能量管理和智能运维平台数据接入方案

EMQ映云科技

物联网 mqtt 碳中和 边云协同 新能源

TCP长连接实践与挑战

字节跳动终端技术

字节跳动 TCP 后端 HTTP 连接池

架构实战营-模块一作业

炎彬

「架构实战营」

为什么 Cpython 是 C 写的,而不是 Python

宇宙之一粟

Python 1月月更

架构训练营作业1

Richard

架构实战营

如何知道自己是否适合做产品经理?

石云升

产品经理 1月月更

微信业务架构图 & 学生管理系统设计

tom

第一次作业

Mr小公熊

实现一个任务调度系统,看这篇文章就够了

勇哥java实战分享

Elastic-job XXL-JOB 任务调度 自主研发

微信架构简析和学生系统初步方案

Bear

「架构实战营」

微信的业务架构&学生管理系统架构设计

随欣所遇

架构实战营

☕【Java深层系列】「并发编程系列」深入分析和研究MappedByteBuffer的实现原理和开发指南

码界西柚

Java 文件I/O MappedByteBuffer FileChannel 1月日更

架构设计小试牛刀

Fingal

架构实战营

LabVIEW仪表盘识别(实战篇—6)

不脱发的程序猿

机器视觉 图像处理 LabVIEW 仪表盘识别

百度AICA再添57位“首席AI架构师”,人工智能与产业场景结合愈发深入

百度大脑

架构实战营模块一作业

刘璐

天津银行周传凯:从 0 到 1,我的分布式数据库落地经验谈

OceanBase 数据库

分布式 OceanBase 开源 OceanBase 社区版 客户实践

从 C 迁移到 Rust 的挑战与经验教训_编程语言_InfoQ精选文章