最好的WebAssembly运行时可能仍然是根本没有运行时
摘要
本文使用支持宽算术的wasm2c对WebAssembly-to-C方法进行了基准测试,证明其在速度和内存使用方面仍然与专门的WebAssembly运行时(如Wasmer和Wasmtime)具有竞争力。
<p><a href="https://lobste.rs/s/jfyhu4/best_webassembly_runtime_may_still_be_no">评论</a></p>
查看缓存全文
缓存时间:
2026/07/09 07:44
# 最佳 WebAssembly 运行时可能依然是“没有运行时”
来源:https://00f.net/2026/07/08/webassembly-compilation-to-c-2026/
2023 年,我写过一篇题为《最佳 WebAssembly 运行时可能依然是“没有运行时”》的文章(https://00f.net/2023/12/11/webassembly-compilation-to-c/)。
简而言之:如果你已经有一个 WebAssembly 模块,将其翻译成 C 代码,再用常规的原生编译器编译那段 C 代码,其效果好得令人出乎意料。
我当时以为随着时间推移,这个论点会逐渐变弱,因为 WebAssembly 运行时一直在不断改进。它们的编译器拥有更好的寄存分配器、更低开销的指令降级、对更新 WebAssembly 指令的更好支持,以及比小型转译器更完善的部署打磨。
然后我跑了 2026 年的基准测试。
在 2026 年的 WebAssembly 基准测试(https://00f.net/2026/06/23/webassembly-runtimes-2026/)中,可以看到其他运行时的完整数据表。
WebAssembly-to-C 路径表现非常好,但还是被实现了“宽算术”WebAssembly 提案的新运行时超越了。
我很好奇,如果实现了这个提案,WebAssembly-to-C 方案会表现如何。
所以,我们来对比一下:
- `Wasmer 7.1.0`
- `Wasmtime 46.0.0`
- WABT 的 `wasm2c`
对于 `wasm2c`,我添加了对宽算术的支持,实现起来出乎意料地简单。你可以在我的分支找到这些代码:这里(https://github.com/dip-proto/wabt)。
生成的 C 代码直接使用了编译器的进位内建函数和 C 的 128 位整数类型。
注意,我是在与之前运行时测试完全相同的 libsodium 基准套件上,采用 WABT 的 Segue 内存模式,用 `zig cc -O3 -march=native` 编译生成的 C 代码。
每次构建都使用 `lime1+simd128+wide_arithmetic` 特性集。
哦,在有人问之前:`wasm2c` 也像其他运行时一样,支持使用保护页(guard pages)的内存保护机制。
## 速度数据
以下数据是相对原生 libsodium 构建的减速比。值为 `1.25x native` 表示该基准测试比原生代码多花了 25% 的时间。
数值越低越好。
带宽算术的 wasm2c,按几何平均减速比排序
中位数与 `Wasmer` 持平。但从几何平均值看,`wasm2c` 可执行文件耗时是 `Wasmer` 的 0.887 倍,是 `Wasmtime` 的 0.814 倍。
简单的 C 路径在特性升级后依然坚挺。一旦加入了新的算术指令,普通 C 编译器就能产生与专门的 WebAssembly 编译器性能相当的代码。
## 内存使用
我还用 `/usr/bin/time -v` 测量了峰值常驻集大小(RSS)。
这是单次命令调用时的冷进程 RSS,取 15 次运行的中位数。它包括运行时可执行文件、其启动状态、该命令使用的 JIT 或编译机制,以及进程在运行基准测试时映射的任何其他内容。
注意,`Wasmer` 和 `Wasmtime` 有一个固定开销,如果服务保持运行时存活并运行多个模块或多次调用,这部分开销可以摊销。
冷进程内存,15 次运行的中位数
大部分差异来自固定的引擎开销。模块本身占用的内存非常少。
如果我减去通过相同路径运行一个空的 WASI 模块(使用与基准测试构建相同的 64 MiB 最大线性内存)的 RSS,只剩下几 MiB:
模块本身消耗多少
运行时,由于 WebAssembly 代码被直接编译为 C,再编译为可执行代码,就没有引擎进程需要携带了。
## 没有运行时仍然是一个好选择
生成的输出是便携式 C 代码。
因此,部署目标不必是“带有 WebAssembly 运行时的平台”。它可以是任何有 C 编译器和足够 libc 支持(以满足模块导入)的平台。
这也意味着编译器是可选的。
你可以用 `clang` 或 `gcc` 编译生成的 C 代码。
但你也可以利用现代 C 工具链,例如 Fil-C(https://fil-c.org/),在 wasm64 上获得内存安全性,或者在 wasm32 上无需依赖虚拟内存保护页。
或者你可以使用经过形式化验证的编译器,例如 CompCert(https://compcert.org/),进行高可信度的代码生成。
我没有对这些工具链进行基准测试。重点是,WebAssembly-to-C 提供了这种选择,而专门的运行时则不行。
这也是为什么我仍然对 `Wasmer` 印象深刻。
`Wasmer` 还实现了 WASIX(https://wasix.org/),它填补了普通 WASI 仍有留下的许多 POSIX 形状的缺口。因此,如果你的目标是减少重写、在 WebAssembly 中运行现有应用程序,这是更好的选择,而不是 WebAssembly-to-C 或其他选项。
而 `Wasmtime` 实现了“组件模型”(Component Model),它改善了代码分离,并为组合 WebAssembly 片段提供了类型化的接口边界。我有一种直觉,通过分别编译组件,也可以使用 WebAssembly-to-C 实现同样的效果,但我没有深入思考。
总之,如果你需要动态加载不受信任的代码而不经预编译、运行时级策略、抢占(preemption)、燃料(fuel)、组件模型机制,或者一个引擎被多个租户复用,那么 WebAssembly-to-C 就是错误的答案。
如果你的部署模型依赖于发布一个便携的 `.wasm` 产物,并让目标机器决定如何运行它,那么 WebAssembly-to-C 也是错误的答案。
但如果你控制 WebAssembly 模块、可以预编译、且不需要运行时服务,那么 WebAssembly-to-C 几乎是无需多想的选择,尤其是在加入了宽算术之后。
相似文章
Lobsters Hottest
本文使用 libsodium 加密库对多种 WebAssembly 运行时(WAVM、WasmEdge、WAMR、wasm2c、Wasmer、Wasmtime、Wazero、Node、Bun)进行了性能基准测试,比较了 2024、2025 和 2026 年的版本。结果显示,WAVM、WasmEdge(AOT)、WAMR(AOT)、wasm2c、Wasmer 和 Wasmtime 在 CPU 密集型加密任务中实现了接近原生的性能,而 wide_arithmetic 指令对加密代码有益。
Lobsters Hottest
本文介绍了WATaBoy,一个Game Boy模拟器,它使用即时编译到WebAssembly的方式,实现了超越原生解释器的性能,是JIT到Wasm在模拟领域的一个概念验证。
OpenAI Blog
Wasmer 利用 OpenAI 的 Codex 构建了 Edge.js,这是一个运行在 WebAssembly 沙箱中的边缘计算 Node.js 运行时,将开发周期从一年缩短至两周。
Lobsters Hottest
SpaceWASM 是一个来自 NASA/JPL 的开源 WebAssembly 解释器,专为航天器机载序列化设计,注重确定性内存使用和在资源受限环境下的流式处理能力。
X AI KOLs Timeline
V8 已经实现了针对 WebAssembly 的投机性 call_indirect 内联和反优化支持,显著提升了性能,特别是对于 WasmGC 程序,在基准测试中速度提升高达 50%。