< 返回版块

苦瓜小仔 发表于 2025-12-30 13:58

Tags:日报

故事:4 个月后加入 Rust compiler 团队的历程

一名身处俄罗斯的开发者通过 171 次贡献正式加入了 Rust 编译团队,并在平衡生计与梦想的过程中获得了社区的温暖回馈。

在发布博客后不久,作者收到了一笔价值 1500 美元的匿名 Solana 代币捐赠。这笔钱相当于他教孩子们学习 IT 三个月的薪水,作者表示这不仅是物质上的支持,更是对他工作价值的巨大认可。

阅读:https://www.reddit.com/r/rust/comments/1pw5i9y/4_months_later_update_on_my_journey_toward_the/

文章《为什么调用具有完全相同汇编的函数,Rust 比 C 慢?》

作者:Ohad Ravid

核心问题:为什么在 Rust 和 C 使用完全相同的汇编代码(SIMD)时,Rust 的调用速度却更慢?

背景:rav1d (用 Rust 编写的 AV1 解码器,是 dav1d 的 Rust 移植版)是通过 c2rust 转换并逐步手工优化的项目。作者在之前的优化中已经让 rav1d 提速了 1%,但在对比 rav1d (Rust) 和 dav1d (C) 的性能快照时,发现了一个诡异的现象:

  • 两个项目使用了完全相同的手写 ARM NEON 汇编函数。
  • 但在一个名为 cdef_filter4_pri_edged_8bpc_neon 的函数中,Rust 版本比 C 版本慢了 30%,导致整体运行时间增加了约 0.5%。

作者通过工具 samply(macOS 上的采样分析器)进行逐行指令级别的分析,解开了谜团:

  • 第一个“为什么”:为什么这个汇编函数在 Rust 中慢?
    • 通过 samply 的汇编视图发现,性能瓶颈出在一条 ld1(加载数据)指令上。在 Rust 环境下,这条指令的耗时远超 C 环境。
  • 第二个“为什么”:为什么加载指令变慢了?
    • 深入分析后发现,Rust 版本在调用该函数前,在栈(Stack)上存储了大量数据。过多的栈操作导致了严重的内存访问开销,甚至可能引起了缓存压力,使得原本简单的加载指令变慢。
  • 第三个“为什么”:为什么 Rust 会在栈上存这么多东西?
    • 问题的根源在于 Rust 的抽象层级
    • 在 C 代码中,函数调用更直接;而 Rust 为了实现安全性或特定的封装,通过函数指针包装器来调用这些汇编函数。
    • 由于这些调用是经过函数指针(间接调用)的,LLVM 编译器无法跨越这个边界进行优化。它无法确定被调用函数会如何使用寄存器,因此只能保守地将大量上下文状态“溢出”(Spill)到栈中。

解决方案:作者发现,通过简化 Rust 的抽象结构,使其对编译器更“透明”,可以解决这个问题:

  • 优化手段:修改了 Rust 中处理 CDEF 过滤器的函数调度逻辑,通过减少不必要的包装层和间接性,让 LLVM 能够识别出寄存器的使用模式。
  • 结果:优化后,编译器能够直接在寄存器中保留数据,避免了繁重的栈操作。Rust 版本的汇编函数调用速度最终与 C 版本对齐。

结论与启示:

  • 汇编相同不代表性能相同:语言的“胶水代码”和编译器对调用约定的处理方式(ABI/Calling Convention)对性能影响极大。
  • LLVM 的局限性:LLVM 在处理复杂的 Rust 抽象(尤其是涉及间接调用或跨语言 FFI 时)时,可能无法自动实现最优的寄存器分配。
  • 工具的重要性:文章强调了像 samply 这种能深入到指令级、并能可视化对比 C 和 Rust 执行差异的工具在底层优化中的关键作用。

该文展示了即使底层汇编完全一致,上层 Rust 代码的抽象方式若阻碍了编译器的寄存器优化,依然会导致显著的性能损失。通过消除不必要的抽象,Rust 可以达到与 C 完全一致的顶级性能。

阅读:https://ohadravid.github.io/posts/2025-12-rav1d-faster-asm/

讨论:https://www.reddit.com/r/rust/comments/1pwzti4/why_is_calling_my_asm_function_from_rust_slower/

文章《The Algebra of Loans in Rust》

这篇文章探讨了如何通过一套“代数框架”来形式化并扩展 Rust 的借用检查系统。

  1. 引入新引用类型:文章提出了两种具有挑战性的引用类型——&own T(所有权引用,拥有数据但不负责释放空间)和 &uninit T(未初始化引用,用于安全的“原地初始化”)。
  2. 借用的代数化:作者将借用行为转化为一套严谨的规则表。这套“代数”定义了当一个引用存在时,原始内存位置(Place)被禁止的行为(如读取、写入、移动),以及借用结束后该位置所处的状态(如:是被初始化了,还是变成了未初始化的空洞)。
  3. 解决痛点
    • &own 解决了在不使用 Box 的情况下,如何在栈上安全地转移引用目标的所有权。
    • &uninit 提供了“输出参数”的安全保障,确保函数调用者提供的空槽位在返回前必须被正确填充。
  4. 理论意义:通过定义这些引用在借用期间对内存权限的动态影响,该框架为 Rust 编译器处理更复杂的内存模式(如从引用中移动数据或部分初始化)提供了统一且可验证的理论基础。

该文章通过严谨的代数推导,为 Rust 未来实现更强大、更具表现力的静态内存管理模型勾勒了蓝图,旨在消除更多场景下对 unsafe 代码的依赖。

阅读:https://nadrieril.github.io/blog/2025/12/21/the-algebra-of-loans-in-rust.html

讨论:https://www.reddit.com/r/rust/comments/1pw8bmf/the_algebra_of_loans_in_rust/

--

From 日报小组 苦瓜小仔

社区学习交流平台订阅:

评论区

写评论

还没有评论

1 共 0 条评论, 1 页