写点什么

从 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:107148

评论

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

写一个开源的 macOS 程序可以赚多少钱?

子骅 luin

node.js redis GitHub 开源 赚钱

为什么你的创业公司应该运行在Kubernetes上

云原生

云原生 k8s

uni-app黑魔法:小程序自定义组件运行到H5平台

崔红保

小程序 uni-app

业务系统开发程序员常用linux知识

程序员劝退师

Linux

近两年影响我的两个重要原则

Selina

测试

Chonge

毕竟,一生很短,少有圆满

霍太稳@极客邦科技

创业 身心健康 个人成长

【gRPC】Python调用Java的gRPC服务

遇见

Java Python gRPC

Linux 性能诊断:负载评估入门

RiboseYim

Linux 性能优化

电子书:《Linux Perf Master》

RiboseYim

Linux 性能优化

分享多年积累的 macOS 效率工具

张晓辉

macos

芋道 Spring Cloud Alibaba 介绍

艿艿

阿里巴巴 分布式 微服务 Spring Cloud Spring Boot

OKR实践中的痛点(1):老板的KR我的O,怎么办?

大叔杨

OKR Scrum 敏捷

此为开卷

X.F

走出舒适区最好办法别走了,扩大它

乐少

业务代码必须要做的事情

程序员劝退师

一文讲清楚 MySQL 事务隔离级别和实现原理,开发人员必备知识点

古时的风筝

MySQL 数据库 事务隔离级别 mysql事务 数据库事务

对话 CTO〡和 PingCAP CTO 黄东旭聊开源数据库新蓝海

ONES 王颖奇

数据库 分布式 开发者

2020了,各家小程序发展的怎么样?

崔红保

小程序 uni-app

【数据结构】双向链表插入操作的时间复杂度分析

遇见

数据结构 算法 时间复杂度

浅谈汽车行业嵌入式软件发布的流程有多复杂

WB

程序员 软件

从流程、认知上做稳定的系统演进

Skysper

系统设计 质量管理

Flink初体验

数据社

大数据 flink 流计算

人们喜欢彼此制造困难让大家难过

Fenng

用声音在一起,听荔枝CTO丁宁聊UGC声音互动平台的技术世界

ONES 王颖奇

内容 企业架构 互联网

一个创业者的途中思考

非著名程序员

创业 读书笔记 程序员 重新理解创业 思考

【Vue3.0 Beta】尝鲜

德育处主任

CSS Java html5 Vue 大前端

初入响应式编程(上)

CD826

spring 微服务 Spring Cloud 响应式编程 reactor

翻译: Effective Go (1)

申屠鹏会

翻译 Go 语言

WebSphere Application Server运维实践 --从入门到监控

rafe

Java WAS perfservlet visualVM JMX

寻找伴侣最重要的是什么?

二爷

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