Ruby Tumbler 项目进展得出奇地好。我比预期走得更远,还学了些关于动态语言的迷人事儿。
不过也比预期慢。
这些实验,我不得不做得更有章法,还得狠想清楚:一个高效的原生 Ruby 编译器应该长什么样。
保住动态行为
因为问题不是把 Ruby 翻译成机器码那么简单。
Ruby 的动态性极端。方法可以在运行时被替换。对象可以改行为。闭包可以比它们的主人活得久。而程序员指望这一切继续运转。
诀窍是保住这些行为,而不是全程为所有机器买单。
于是有了我最喜欢的实验之一:受 Rust 启发的内存管理,但不要求 Ruby 开发者去想所有权场景。
编译器能证明一个块不会逃逸,借走它捕获的存储,免掉一次堆上闭包的分配。运行时,它检查实际要执行的那个方法。
如果那个实现没被证明安全,就回退到普通的堆管理行为。
没有所有权注解,没有生命周期声明,也不用重写你的应用。
受控测试里,这套流程每次调用省掉两次分配,计时批次里的垃圾回收全部消失。我对这事拼起来的样子相当满意。
我还在朝 CRuby 兼容推进。有些动态特性要多干些活,实验性的原后端还没法完整启动 Rails。我用随机挑的六个 Rails 仓库做测试,大小和复杂度各异——因为我宁愿现在就撞上那些别扭的边角案例。
我的目标是不逼开发者为了原生编译这一点甜头去做大重构。
与此同时,引擎盖下,整台机器都变了。
成类别的运行时工作被合并、特化,或者干脆消灭。
我正在意识到,重要的问题往往不是怎么把单个 Ruby 操作变快,而是怎么从一开始就不执行那些不必要的操作。
现阶段,我把运行时性能和内存管理排在编译速度前面。
基准展示了什么
快了多少?
内部基准在我探索的这些工作负载上,仍然显示大约 3× CRuby。
这远不是定论。有些操作提升大得多,有些小些。我还不能许诺说,某一类 Rails 应用在所有场景下都能加速……暂时还不能。
但有件有意思的事正在发生。
这堆东西起初只是一组实验,现在有了一套连贯的哲学,围绕动态分派、内存所有权,以及保住语言语义而不背不必要的开销。
它开始不像一堆实验,更像一个真正连贯的产品了。
取决于工作排期,我可能还需要几周才能把仓库发出来。
这事让我兴奋。
等不及想分享。
我爱你们所有人。