我们把Rust代码用TypeScript重写了一遍,结果快了3倍
Rust很慢。
放在两年前,这句话足够让一个Rust开发者掀桌子。但今天,OpenUI团队的工程师们平静地写下这句话时,手里握着冰冷的 benchmark 数据:TypeScript 比 Rust + WASM 快了 4.6 倍。
这不是什么业余爱好者的玩具项目。这是一个运行在 LLM 流式输出上的生产级解析器,每毫秒都决定着用户感知的卡顿。他们原本的逻辑无懈可击:Rust 快,WASM 接近原生速度,解析器又是复杂的多阶段流水线。用 Rust 写,然后编译到浏览器,天经地义。
结果他们优化错了方向。
真正的瓶颈不在 Rust,而在那道"墙"
这个解析器有六个阶段:自动补全、词法分析、分割、解析、解析、映射。听起来很硬核,对吧?
问题是,它跑在浏览器里。
每次调用 WASM 解析器,都要经历一场漫长的"跨国旅行":
JS 堆 → 拷贝到 WASM 线性内存 → Rust 解析 → 序列化成 JSON → 拷贝回 JS 堆 → V8 反序列化
Rust 本身的执行时间,在这段旅程里几乎可以忽略不计。 真正的性能杀手,是 JS 和 WASM 之间那道内存边界。两个运行时,两套内存布局,每次交互都要过海关、填表格、等安检。
团队尝试了一招看似聪明的优化:用 serde-wasm-bindgen 直接返回 JS 对象,跳过 JSON 字符串的编解码。
结果慢了 30%。
为什么?因为构造 JS 对象需要 Rust 运行时递归地把数据一点点"翻译"成 V8 能理解的格式,这制造了比 JSON 方式更多的细粒度边界穿越。JSON 方案虽然看起来笨重,但 serde_json::to_string() 在纯 Rust 里零边界成本跑完,然后 V8 的 C++ 层用原生速度一次性解析。
大而重的单次操作,打败了小而频繁的跨界通信。
TypeScript 的逆袭
他们索性把整个流水线移植到了 TypeScript。
六阶段架构原封不动,输出格式一字不改,只是剥离了 WASM,让代码完全运行在 V8 堆内。
结果让人窒息:
| 测试用例 | TypeScript | WASM | 提速 |
|---|---|---|---|
| 简单表格 | 9.3µs | 20.5µs | 2.2x |
| 联系表单 | 13.4µs | 61.4µs | 4.6x |
| 仪表板 | 19.4µs | 57.9µs | 3.0x |
消除边界开销后,每调用一次解析函数,TypeScript 都碾压 WASM。没有内存拷贝,没有序列化,没有跨语言翻译,JIT 编译后的 JavaScript 在这个特定场景下,比号称"零成本抽象"的 Rust 更贴近金属。
说实话,这有点反直觉。但数据不会撒谎。
还有更深的坑
如果故事到这里结束,那只是一篇"JavaScript 比 WASM 快"的猎奇文章。真正的工程师思维,在于发现更底层的愚蠢。
团队意识到,他们的流式架构有个算法层面的硬伤。
解析器在 LLM 的每个输出块上都被调用。原始做法是累积所有块,然后从头重新解析整个字符串。20 个字符的块,累积到 1000 字符,总共要处理约 25,000 个字符的重复解析。O(N²) 的复杂度。
他们改了策略:语句级增量缓存。
利用 DSL 的语法特性(深度为 0 的换行符表示语句结束),一旦某个语句完成,就缓存它的 AST,再也不重新解析。只有最后一个不完整的语句需要处理。
结果:
| 测试用例 | 原始流式 | 增量缓存 | 提速 |
|---|---|---|---|
| 联系表单 | 316µs | 122µs | 2.6x |
| 仪表板 | 840µs | 255µs | 3.3x |
算法复杂度从 O(N²) 降到 O(N),带来的提升比语言切换更猛烈。
教训与余波
这场重构留下了四个血淋淋的教训:
第一,先分析瓶颈在哪里,再选语言。 他们的成本从不在计算,而在数据传输。
第二,"直接传对象"不是银弹。 细粒度的 FFI 调用比批量序列化更昂贵,哪怕后者看起来多了一层转换。
第三,算法优化碾压语言优化。 从 O(N²) 到 O(N) 的改进,比从 Rust 到 TypeScript 的切换影响更大。
第四,WASM 和 JS 不共享堆。 这是架构层面的隔离,任何转换都有成本。
有趣的是,评论区有人提到了 1997 年的一段往事:把 C++ 批处理代码移植到 Python 1.4,结果快了 10 倍。原因类似:C++ 代码里堆砌了几个月的"优化",输给了 Python 更聪明的算法和数据结构。
历史总是押韵。
所以,Rust 不好吗?不,Rust 很好。WASM 不行吗?不,WASM 很强大。但在错误的地方用正确的技术,就是错误。
当边界穿越成本高于计算成本时,最快的代码就是不要穿越边界。哪怕那意味着用"慢"语言写"快"代码。
【kimi-k2.5锐评】:当工程师们终于意识到"零成本抽象"敌不过"零跨界调用"时,这场Rust神话的祛魅,本质上是对现代软件复杂性的一次诚实体检。
参考链接:
https://www.openui.com/blog/rust-wasm-parser