
Relax:为Rust打造的JIT编译器,边编译边运行Cargo包
来源标题:Relax – a JIT for Rust that runs Cargo packages before it is done compiling
Relax是一个用Rust编写的实时编译器和JIT运行时,能在编译过程中直接运行Rust程序,无需rustc、LLVM或链接器。它按需逐个编译函数,程序运行中优化热点代码,保存文件时把新代码热补丁进运行中的进程并保留状态。官方数据显示,大型3D游戏在M4 MacBook上从源码首次出帧仅11秒,优化后代码速度为LLVM的1.14至1.26倍。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
不用等 Rust 编译完成。rustc 先把程序构建出来,然后你才能运行。Relax 则是一边构建一边运行。
Relax 是一个用 Rust 编写的 Rust 实时编译器和 JIT 运行时。它直接从源码运行 Cargo 包,整个过程中不需要 rustc、LLVM 或链接器。它会检查你的代码,在函数第一次被调用时编译它,在程序运行期间优化热点函数,并在你保存文件时把新代码修补进正在运行的进程。
TL;DR 拥有 Rust 的速度和安全性,却不用等编译。
rustc + Cargo Relax 大型 3D 游戏,全新检出 → 第一帧 约 3 分钟构建,然后启动 在 M4 MacBook 上 11 秒,直接从源码启动 同一个游戏,第二次运行 → 第一帧 启动已构建的二进制 在 M4 MacBook 上 1.15 秒,没有重新编译 在运行中的程序里修改一个函数 重新构建、重新链接、重启:程序状态丢失 修补进正在运行的进程:状态保留 保存一个文件 → 92 个测试重新运行 先重新构建测试二进制 保存后 2.2 秒 小型应用,冷启动 → 第一帧 先构建 冷启动 3.7 秒,热启动 0.41 秒(M4 MacBook) 优化后代码的速度 1.00×(LLVM,--release 针对原生 CPU) 在真实负载上为 1.14–1.26×;在密集数值循环上为 2.3×,因为它目前还不会向量化(详情) 运行前进行类型和借用检查 是 是 依赖 rustc、LLVM、链接器 Relax,一个 Rust 库(438k 行,0 个 crates.io 依赖)
代价是什么:一套新的 Rust 工具链,从源码字节到机器码
Relax 不是 rustc 的封装,也不是 LLVM 的插件。它取代了这两者,以及 Cargo 和链接器中位于你的源码与运行程序之间的部分,全部集成在一个无依赖的 Rust 库中。
Rust 给你速度和安全性,代价是编译时间。Relax 保留速度和安全性,去掉这个代价。
AI agent 感受不到缓慢的构建,但每一次构建都要付出 token、实际耗时和上下文丢失的代价。Makepad 构建应用时会让许多 AI agent 并行工作,每个 agent 在自己的通道里,Relax 让它们保持快速。它的开发中心 devhub 围绕 Relax 运行构建和测试机器,因此 agent 接触到的任何东西都不会是冷的:
flowchart LR A["AI agent<br/>编辑一个文件"] -->|"同步,约 50-200 毫秒"| B["devhub<br/>路由到一台热机器"] B --> C["实时 Relax 会话<br/>只修补变化的部分"] C --> D["测试重新运行<br/>几秒内出结果"] C --> E["应用帧<br/>抓取为 PNG"] D --> A E --> A C -->|"飞行记录器"| F["为什么慢,<br/>编译了什么"] F --> A 加载中
每条通道一个实时会话。每个 agent 的应用或测试运行都在池中的机器上于 Relax 下保持存活,每次保存都会修补它。无需重新构建或重新启动。 处处都是热缓存。每台机器在变更落地时刷新其 Relax 缓存,因此新的 worker 启动时就是热的。 循环中不涉及原生构建。devhub 将迭代和落地前检查发送给 Relax,并按通道统计每一个漏过去的原生构建。 基于测量的路由。任务按其测量时间分大小,快速检查会送到有热会话的机器,因此它们永远不会排在长构建后面。
在实践中:九条 agent 通道并排运行,devhub 每小时处理超过 300 个构建和测试任务,每天有数十个变更落地,每个在合并前都在 Relax 下检查。
rustc + Cargo Relax 启动 构建整个 crate 图,链接,然后运行 main。 解析 crate 图并从源码启动 main。不原生构建你的代码。 检查 预先对每个 crate 进行类型和借用检查。 同样,在第一次调用之前。 编译 整个 crate,提前编译。 一次一个函数,在第一次被调用时。热点函数在后台升级:解释器 → O1 → O3。#[relax(O2)] / #[relax(O3)] 可以提前请求某个级别;在 rustc 下该属性不起作用。 编辑 重新构建并重启。 重新检查、重新编译,只换入变化的部分,程序保持运行。已经分发的代码保持有效。无法换入的变更会在原地重启程序。 缓存 每个 crate 的增量产物。 每个结果以其输入哈希为键。未变化的包不重新编译任何东西。 过程宏 构建为原生 dylib,然后加载。 当它们匹配链接构建时原生运行,否则在 JIT 上运行。 标准库 以预编译形式提供。 core、alloc 和 std(134k 行)由 Relax 自身从源码编译。 原生代码 一切都是原生的。 必须保持原生的 crate(操作系统层、GPU、音频)通过 #[relax_api] 导出接口。JIT 代码通过 C ABI 规划器调用它们,跨边界进行 per-thread 堆路由。 可见性 事后编译器计时。 实时飞行记录器,带卡顿事件和编译分析器。 后端 LLVM,然后是系统链接器。 自己的 x86-64 和 AArch64 代码生成器,没有链接器,没有 LLVM。
LLVM 仍然胜出的地方
LLVM 背后有数十年的优化工作,在稳定状态下它的代码仍然比 Relax 的快。我们自己的测量:热 JIT 与相同源码和输入的本地构建对比,以实际耗时比值表示(越低越好,1.00× = LLVM)。
哪个 --release?大多数 Rust 使用普通的 --release 发布,为便携基线 CPU 构建(在 x86-64 上,没有 AVX)。我们的数字是针对更严苛的设置,--release 加上 -C target-cpu=native,此时 LLVM 可以使用机器拥有的每一条指令。Relax 总是为它运行的 CPU 编译,所以与普通 --release 相比,这些差距会缩小,在向量密集的代码上它们可能反转。那个比较尚未测量。
差距来自哪里,来自相同函数的指令级比较。差距是额外的指令而不是停顿:1.26× 的指令只带来 1.15× 的周期。
不是原因的东西:内联。Relax 比 rustc 内联得更多。
这张表中的每一行都是一个工作项,切片器差距在过去三天里从 1.26× 降到了 1.14×。
rustc 把你的程序交给 LLVM 和链接器,Cargo 把步骤粘合在一起。Relax 拥有整个流水线,从源码字节到可执行内存,都在一个进程中。
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。