CodingTour
关于 Rust 的一些分享

Rust 生态

🔥 Crates 每日下载量,7 天平均值:

crates.io 目前单天下载量在 1.44 亿,超过了自 2015 年 5 月发布 Rust 1.0 以来到 2022 年的总下载量,其生态从 2020 年起,每年下载量以 1.7 倍速度增长。

🔥 crates.io 上的 用户/团队 数:

目前有 37000 左右的 用户/团队 将他们的 crate 发布在公网上,每年增长率为 1.3 倍左右。

根据 Lib.rs 的统计,目前 Crates 数量约为 14 万。作为对比,服务于 Swift 和 Objective-C 的 iOS 依赖管理软件,CocoaPods,目前的 Pods 数量约为 10 万。

并且其生态能覆盖到 C/C++/Java/Go 等语言的应用领域,作为其他语言更底层的基础设施而存在:

总结,Rust 的生态在快速发展中。

市场前景

Rust 近些年来也逐渐在工业实践中崭露头角,例如:

  • Mozilla:使用 Rust 开发其旗下 Firefox 浏览器的 CSS 引擎 —— Stylo
  • Dropbox:使用 Rust 编写其核心的文件存储组件
  • Discord:为了解决其延迟峰值的问题,直接使用 Rust 重构了其原先的 Go 代码

从下图中可以看到,很多知名公司都开始在团队中使用 Rust:

一些实践分享:

Google 工程总监:“Rust 团队的生产力是使用 C++ 的团队的两倍。”

Rust 和 go 语言一样有很高效率的生产力,Rust 团队的生产力是使用 C++ 的团队的两倍。

Rust 第一印象

Rust 在 Android 平台上比 Java、Kotlin 执行更快

Rust Storage 模块在稿定 App 里的性能表现:

写性能,4 倍以上

读性能,1.5 倍以上

本来我们的存储组件性能监控是以 ms 为单位,但在对比原生平台性能时,0 和 1 之间的实际差异看不出来,故改为了 us。

为什么更快?

Rust Compiler Development Guide — 一个 Rust 程序是如何从源文件编译为二进制文件的

没有 JVM、GC 开销

zero-cost abstractions

两个理念:

  1. 你不需要为不需要的东西付出额外的代价
  2. 你需要的东西,你手写的代码无法比抽象后的性能更好

几个例子:

  1. 累加器
pub fn sum(n: i32) -> i32 {
    let mut sum = 0;
    for i in 1..n {
      sum += i;
    }
    sum
}

汇编形式:

example::sum:
        xor     eax, eax
        cmp     edi, 2
        jl      .LBB0_2
        lea     eax, [rdi - 2]
        lea     ecx, [rdi - 3]
        imul    rcx, rax
        shr     rcx
        lea     eax, [rcx + 2*rdi]
        add     eax, -3
.LBB0_2:
        ret

看两个关键的寄存器:

  • RDI 寄存器 - 存储参数 N 的地方
  • RAX 寄存器 - 存储计算结果的地方

即:(N - 2) * (N - 3) / 2 + 2 * N - 3(N - 1) / 2 * N ,一个数学多项式。

  1. HashMap vs HashSet

  1. 迭代器
let numbers = vec![1, 2, 3, 4, 5];
// Chain iterators to transform the items without runtime overhead
let doubled: Vec<_> = numbers.iter().map(|&x| x * 2).collect();

assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
  1. Rust struct vs Java class

构建一个抽象时,这个抽象不会造成额外的负担,如 Java 的类 A 里有类类型 B 的成员,那么通过这个 A 类对象访问 B 成员事实上需要两次指针访问。但 Rust 的 struct,是直接在栈上一次访问,虽然做了抽象,但是和不抽象直接把东西放一起是一样的,并没有为抽象支付成本。

总的来说,用 Rust 写的代码,能编译通过基本上就很放心了,内存问题、并行编程问题、指针问题等等,性能上借由编译器,也能有不错的结果表现。

Rust 在前端

为什么 Rspack 比 Webpack 快那么多

Rspack 是字节跳动开源的基于 Rust 的前端构建工具,它可以与 Webpack 生态系统交互,并提供更好的构建性能,在处理具有复杂构建配置的巨型应用中(依赖模块上万),可以提供 5~10 倍的编译性能提升。目前在 GitHub 上有 7400+ star。

官方给出的 Benchmark:

  • production,耗时缩短将近九成
  • dev,npm run dev 是开发者每天需要运行很多次的命令,在 Rspack 的加持下,该耗时缩短了 87%

它的架构和 Webpack 是一致的,由两个核心阶段驱动:

  1. make 阶段,主要分析项目依赖,生成模块依赖图
  2. seal 阶段,主要是做代码产物优化以及最终产物生成,优化包括:
    1. tree-shaking,类似 mark-sweep 算法,将不会被执行的代码删除
    2. code-splitting & bundle-splitting,分包策略,通过若干 chunk 提高浏览器的加载速度和 CDN 的缓存命中率
    3. minify

做的事情差不多,但 Rspack 对具体的任务执行做了很多多线程的并行加速,这符合目前绝大多数前端工具 native 化的策略

  1. 和目标移植工具在设计上、Javascript API 上保持兼容
  2. 尽可能提高速度

字节在工具链的选型上选择了 Rust,候选人有 JavaScript(Node.js)和 Golang。

Why not JavaScript(Node.js)

核心原因是多线程、并行效率不高:

  1. Node.js 单线程优化的潜力不大,好在 Node.js 提供了多线程能力。
  2. Node.js 虽然通过 worker-thread 提供了多线程,但由于它是通过创建新的 V8 实例来模拟的多线程,所以这些 V8 实例没办法共享内存,如果你想做线程间通信,只能依赖消息传递
  3. 用 worker-thread 消息传递时,所有的消息结构都需要深拷贝,没办法直接将对象移动到另一个线程,这一定程度上增加了通信的开销

其次是 Node.js 的并发编程生态比较差,它的底层数据结构没有无锁化的支持,也没有相应的并发原语,只支持几种基本的原子类型等等。

这里的效率差有数十倍。

Why not Golang

核心原因是对 Web 生态的兼容性不足:

  1. 由于语言定位和本身生态的原因,Golang 对 napi 支持不好,这不仅影响性能,还意味着对 Webpack 生态做不到完美兼容
  2. 虽然 Golang 生态内也有基本的前端构建工具基础设施,如 Javascript parser、CSS parser,也可以做一些简单的分析,但不支持将 ES6 转译到 ES5,而国内的浏览器版本普遍不是很高,转译是必须的,就不得不再找一些其他 transpiler 来做这件事,这会增加额外消耗,两次 transpile 严重影响性能

反之,Rust 没有这些问题:

  1. 性能够强
  2. napi 支持够好
  3. SWC(Speedy Web Compiler) 提供了丰富的能力,自然也支持 ES5 的转译

桌面端 Electron、Node.js 环境下如何集成,它的优势是什么

Rspack 选择 Rust 的其中一个原因是对 napi 支持的足够好。

为什么 napi 这么重要?

因为 Web 生态里的 API 非常灵活,除了字面量和对象类型,还支持传递函数来做运行时动态配置。

使用传统的 IPC 也可以模拟函数调用,但我们要在 native 侧调用一个 Javascript 的函数时,得先把参数序列化,通过 IPC 传递到 Javascript,然后 Javascript 这边再进行反序列化,最后执行 Javascript 函数再将返回值传输回 native 侧,一次函数调用需要两次跨进程通信

函数调用次数有可能和模块的数量成正比,当模块数量比较大的时候这些额外消耗就变得无法忽略。而 napi 可以将函数指针传递到 native 侧从而降低进程间通信的成本。

https://napi.rs/

  • 🚀 Bring native performance for Node.js
  • ⚡️ Zero copy data interactive between Rust & Node.js via Buffer and TypedArray
  • ⚙️ Parallelism in few lines

它的原理

.node 文件的原理,虽然它的后缀是 .node,但实际上是原生平台的构建产物,比如在 macOS 上是 Mach-O:

以图片处理 sharp 包为例,真正执行任务的还是 C++ 代码: https://github.com/lovell/sharp/tree/main/src,平台相关的处理则在这里: https://github.com/lovell/sharp/blob/main/lib/libvips.js#L42。

而 napi-rs 的工作,则是在 .node 之外,多包装了一个平台环境的调度,类似这样:

然后就可以在 JS/TS 文件中直接引用了:

在 Node.js 环境下使用 require() 加载一个 C++/Rust 编写的动态库时,实际上是通过 N-API 来调用这个库的,调用由 3 部分组成:

  1. C++/Rust 动态库中,需要先定义一个初始化函数,函数的目的是用 N-API 的 API 来创建 JS 对象、函数,并将它们导出到 JS 环境中
  2. 当在 JS 里通过 require() 方法加载这个库时,Node.js 就会先调用这个初始化函数
  3. 初始化函数会返回一个表示模块导出的对象,这个对象就是 require() 方法的返回值,然后就可以在 JS 中直接使用它了

napi-rs 依赖了 Node.js 的这个能力,由于它的定位是 Node.js、Rust 之间的桥,所以也确实能在源码中找到 Rust 的 N-API 实现:

https://github.com/napi-rs/napi-rs/blob/184806c5a4df1dcec67f78b25830b546993bbfdf/crates/sys/src/types.rs#L295

从这一点上看,napi-rs 大幅简化了在 Rust 建立桥的成本,实际上它是很薄的一层。

Rust 在 AI

推理

端侧,尤其是以 RISC-V 为基础架构的智能终端是 Rust 一直以来深耕和擅长的领域,前不久 Vivo 发布的用 Rust 全新构建的 BlueOS,主打的就是新一代 AI 操作系统。

LLM 时代的模型很大,推理很慢,对性能很看重,随着 LLM 的进一步发展,性能必定会变得更加重要,Rust 由于其优秀的语言特性,能接到这一棒。

比如 tract,这是一款为嵌入式而生的推理引擎,它背后的理念是:

模型推理是计算密集型的任务,神经网络背后都会涉及到卷积和矩阵运算。tract 为了提供高性能和跨平台,利用 Rust 和 SIMD,以及内联汇编技术,来优化卷积和矩阵运算。比如自 2014 年至今移动 SoCs 最广泛使用的 CPU 架构 Cortex-A53,以及 苹果 M1 采用的 ARMv8 芯片,如果想充分利用这类芯片的性能,则需要汇编的加持。

不过随着 Rust 加入 Linux 内核,以及 Hugging Face 的大量使用,当然也有 Rust 自己在 GPU 领域的推动,Rust 在 Server 端可能也会有一席之地。

Vivian 在 GOSIM 的演讲 Rust + Wasm 是 AGI 的语言吗 中演示了如何使用 Rust + Wasm 在 2MB 包中运行巨大的语言模型,未来是轻量级的: https://www.youtube.com/watch?v=kY68YwJyzdQ。

中间件

准确来说是和 AI 大模型相关的中间件,首当其冲的是向量检索相关库,这里有大名鼎鼎的 Qdrant 了,性能优秀,而且易于使用,顺带提一下对标全文检索框架 ElasticSearch 的 melisearch,经过多年的发展已经是比较成熟的框架了,这个领域还有很多其他框架,比如 tantivyToshilnxwebsurfx 等。

还有将全文检索、语义检索融合到 SQL 搜索的 paradedb,处理表格的 polars、可视化 pipeline 的 vector、文档图数据库 surrealdb、时序数据库 ceresdb,当下火热的 Agent smartgpt

这块的应用范围其实非常广泛,除了基础组件,可以想象的内容还很多,比如记忆模块、任务调度、资源池、任务定义、流程设计等等,这些组件几乎都是围绕着 LLM 使用的,LLM 带来的远不止这些,随着应用层的不断丰富和发展,还会衍生出更多的需求。

训练

Rust 开始做推理,自然也有人把它放到训练侧,不过目前看起来这块还处于尝试和起步阶段。从 Rust 自身来看,它在相对稳定的工程领域内使用更好,在算法领域普及很难。

对稳定的工程领域来说,无论哪种语言,都会提供简单易用的 API 或指令集,使用者大多数时候只需要根据要求准备好数据即可。但对于算法领域,经常需要涉及底层算法架构的调整和修改,需要新加入或去掉一些模块,这方面 Python 具备绝对优势,做起来也很方便。

从历史上看,Torch 一开始是 lua 写的,不温不火,后面加了 Python 后,慢慢打败了 Caffe、TensorFlow,坐在了第一把交椅上。Rust 如果要向当年的 Torch 一样,它的优势会在哪里?接口大概率还是和现在的 PyTorch 接近,就像 transformers 库流行后,PaddleNLP、ModelScope 的接口不能说和其很像,只能说一样了。对使用者来说,迁移是没必要的,除非不得不这样做,比如在端侧训练,也许对 Rust 来说是一个不错的方向。

语言

Musk 说 Rust 是 AGI 时代的语言:

但除了 Rust,还有专为 AI 而生的 Mojo,它的定位是 Python 的易用性 + C 语言的性能,虽然 Mojo 目前还处于极其早期阶段,但这至少是个苗头:在 AI 主导的未来,说不定会有更 AI Native、LLM First 的语言设计出来。

不过,我们先关注 Rust 吧。

写 Rust 的体验是怎样的

🔥 可在 Android Studio、Xcode、VS Code 里同时写 Java/OC + Rust,并支持断点调试,摒弃了过去在一个仓库开发完,发布,在另一个仓库接入 & 测试带来的繁琐及低效,极大地提高了开发效率。

实现这一切的背后主要归功于 LLDB,调试原理:

最后

目前对 Rust 的用人需求通常也伴随着对某块儿特定领域的要求,比如有数据分析处理的经验,大家愿意花时间投入到 Rust 语言的首要原因还是因为 Rust 能够构建相对正确且无错误的软件,其次才是性能优势。社区里也有很多人担心 Rust 会变得过于复杂,以及担心 Rust 在科技行业没有足够的使用率,对于这两个问题,只能说未来如何,就拭目以待吧~