libsodium WebAssembly 基准 2024→2026:9 个运行时,wide_arithmetic 把 Wasmtime 从 2.41x 拉到 1.46x
信源:WebAssembly runtimes performance in 2026(前作:2019、2021、2023)
各运行时发布页:
- WAVM — https://github.com/WAVM/WAVM/releases
- WAMR — https://github.com/bytecodealliance/wasm-micro-runtime/releases
- WasmEdge — https://github.com/WasmEdge/WasmEdge/releases
- WABT(wasm2c)— https://github.com/WebAssembly/wabt/releases
- Wasmer — https://github.com/wasmerio/wasmer/releases
- Wasmtime — https://github.com/bytecodealliance/wasmtime/releases
- Wazero — https://github.com/tetratelabs/wazero/releases
- Node — https://nodejs.org/en/download/releases
- Bun — https://github.com/oven-sh/bun/releases
要回答的问题很具体:同一份 C 加密代码编译成 WebAssembly,在最新运行时、一年前运行时、两年前运行时上跑,性能是不是真的提升了。不是看某个微基准能不能打败原生,也不是比谁的图好看。
结论先放这儿:
- WAVM 和 WasmEdge 可以非常快。WasmEdge 0.17.0 必须显式指定
--run-mode=aot,否则编译后的模块会按解释器模式运行。 - WAMR 的 AOT 模式也很快,与 WAVM 和最好的 Wasmtime 结果并列。
- wasm2c、Wasmer、Wasmtime 都足够接近原生,对 CPU 密集型加密任务有吸引力。
- Wazero 慢,但稳定。
- Node 和 Bun 那几行需要重跑更长的基准循环——短循环没能充分预热 JIT。
- 实验性的 WebAssembly
wide_arithmetic指令对支持它的运行时意义重大。
测什么
测试程序是 libsodium 的基准套件,基于提交 8e3be8615ba6adcd7babaecf5e76f516890ba5fb 构建。
构建了原生基线和几个 WebAssembly 变体:
- 原生 x86-64,用 Zig 以本地 CPU 为目标编译
- 普通 WebAssembly
- 带 lime1 的 WebAssembly
- 带 lime1 和 simd128 的 WebAssembly
- 带 lime1、simd128 和 wide_arithmetic 的 WebAssembly
原生参考用 -Dcpu=native 构建。wasm2c 生成的 C 代码用 zig cc -O3 -march=native 编译。
WAMR 走 AOT 模式:wamrc 把每个 .wasm 编成 .aot,再由 iwasm 运行。wamrc 不接受 --cpu=native,所以用 --target=x86_64 --cpu=x86-64-v4 --opt-level=3,与主机的可用 x86-64 功能级别匹配,在能编译这些模块的 WAMR 版本上都能工作。
原生命令:
zig build -Denable_benchmarks -Doptimize=ReleaseFast -Dcpu=native -Diterations=3
WebAssembly 命令形式相同,换成 wasm32-wasi 目标和对应的 CPU 字符串:
zig build -Denable_benchmarks -Dtarget=wasm32-wasi -Doptimize=ReleaseFast -Diterations=3
zig build -Denable_benchmarks -Dtarget=wasm32-wasi -Doptimize=ReleaseFast -Dcpu=lime1 -Diterations=3
zig build -Denable_benchmarks -Dtarget=wasm32-wasi -Doptimize=ReleaseFast -Dcpu=lime1+simd128 -Diterations=3
zig build -Denable_benchmarks -Dtarget=wasm32-wasi -Doptimize=ReleaseFast -Dcpu=lime1+simd128+wide_arithmetic -Diterations=3
主机是 AMD Ryzen AI 9 HX 470,12 核 24 线程。CPU 睿频已禁用,最大频率 2 GHz。系统 Linux 7.1.0-rc7,Zig 0.17.0-dev.948+e949341b7。
下面的数字是每个基准相对于原生构建的几何平均减速比,越低越好。2.0 表示“在这台机器上比原生慢两倍”。ITERATIONS=3 意味着 libsodium 那些极小的测试会有噪声和量化效应;报告时间为零的行已从聚合中剔除。进程没有绑核。用来比较运行时的大致行为没问题,最后一位小数不必当真。
版本矩阵
除 WAVM 外,所有运行时都取了 2026 年 6 月 23 日可用的最新稳定版,以及大约一年前和两年前的稳定版。
| Runtime | 2024 | 2025 | 2026 |
|---|---|---|---|
| Bun | 1.1.16 | 1.2.17 | 1.3.14 |
| Node | 22.3.0 | 24.2.0 | 26.3.1 |
| WAMR | 2.1.0 | 2.3.1 | 2.4.4 |
| WABT wasm2c | 1.0.35 | 1.0.37 | 1.0.41 |
| WasmEdge | 0.14.0 | 0.14.1 | 0.17.0 |
| Wasmer | 4.3.2 | 6.0.1 | 7.1.0 |
| Wasmtime | 22.0.0 | 34.0.0 | 46.0.0 |
| WAVM | n/a | n/a | nightly/2026-04-05 |
| Wazero | 1.7.3 | 1.9.0 | 1.12.0 |
WAVM 的历史对比有点麻烦:旧的可用 nightly 对 2024 和 2025 两个时间段都退化成了 2022 年的二进制,而这个二进制拒绝在这台机器上运行。只保留 2026 年的 nightly。
WAMR 2.1.0(选定的 2024 版本)能装,但它的 AOT 编译器在这些 Zig 生成的模块上失败,报 invalid WASM stack data type。版本留在矩阵里,聚合结果不含它。
基准 WebAssembly(无特性)
普通 WebAssembly 构建,没有 lime1、SIMD 或 wide arithmetic。
| Runtime | 2024 | 2025 | 2026 |
|---|---|---|---|
| WAVM | n/a | n/a | 1.41 |
| WAMR AOT | n/a | 1.59 | 1.57 |
| WasmEdge | 1.66 | 1.98 | 1.74 |
| wasm2c | 2.01 | 2.08 | 1.86 |
| Wasmer | 2.13 | 2.56 | 2.08 |
| Wasmtime | 2.67 | 2.54 | 2.41 |
| Wazero | 4.84 | 4.70 | 4.72 |
| Node | 8.60 | 8.22 | 7.95 |
| Bun | 27.41 | 26.42 | 8.77 |
没有普遍趋势。
Wasmtime 稳步提升:2.67 → 2.54 → 2.41 倍原生。不算革命,但确实在进步。Node 也在缓慢提升,8.60 → 7.95。Wazero 基本持平:4.84 / 4.70 / 4.72。WAMR 的 AOT 模式 2025 年就已经很快,2026 年稍快:1.59 → 1.57。Wasmer 在 2025 版本上退步,2026 年恢复。wasm2c 在 2026 年有适度提升——如果提前把 WebAssembly 翻译成原生 C 符合部署模型,它仍然是最佳选择之一。
Bun 是异常值:2024 和 2025 的结果远远落后,2026 年比 2025 年快约三倍,但仍比 Node 慢,方向是好的。
WasmEdge 也快,但它的命令行行为变化足够大。第一次跑 0.17.0 时不小心让编译后的模块走了解释器模式,结果异常慢。加上 --run-mode=aot 后恢复正常:2026 年 1.74 倍原生,介于 2024 和 2025 的结果之间。
按年份的最佳支持构建
上面的表处处比较同一个 WebAssembly 目标,便于横向对照。真要为自己的部署选运行时,更关心的是该运行时实际能跑的最快构建。
所以对每个运行时和年份,还从支持的构建里挑出最好的完整结果:baseline、lime1、lime1+simd128、lime1+simd128+wide_arithmetic。
| Runtime | 2024 best | 2025 best | 2026 best |
|---|---|---|---|
| WAVM | n/a | n/a | 1.41 (baseline) |
| WAMR AOT | n/a | 1.42 (lime1+simd128) | 1.42 (lime1+simd128) |
| WasmEdge | 1.62 (lime1+simd128) | 1.64 (lime1) | 1.64 (lime1) |
| wasm2c | 2.01 (baseline) | 2.08 (baseline) | 1.86 (baseline) |
| Wasmer | 2.09 (lime1) | 2.49 (lime1) | 1.33 (lime1+simd128+wide_arithmetic) |
| Wasmtime | 2.60 (lime1+simd128) | 1.52 (lime1+simd128+wide_arithmetic) | 1.46 (lime1+simd128+wide_arithmetic) |
| Wazero | 4.84 (baseline) | 4.64 (lime1) | 4.71 (lime1+simd128) |
| Node | 8.60 (baseline) | 7.99 (lime1) | 7.95 (baseline) |
| Bun | 27.35 (lime1) | 26.23 (lime1) | 8.77 (baseline) |
按最佳支持构建排序,2026 年排名:
- Wasmer (lime1+simd128+wide_arithmetic) 1.33
- WAVM (baseline) 1.41
- WAMR AOT (lime1+simd128) 1.42
- Wasmtime (lime1+simd128+wide_arithmetic) 1.46
- WasmEdge (lime1) 1.64
- wasm2c (baseline) 1.86
- Wazero (lime1+simd128) 4.71
- Node (baseline) 7.95
- Bun (baseline) 8.77
CPU 特性变体
WebAssembly 特性的故事比运行时年份的故事更有意思。2026 年版本的聚合减速:
| Runtime | baseline | lime1 | lime1+simd128 | lime1+simd128+wide_arithmetic |
|---|---|---|---|---|
| WAVM | 1.41 | 1.59 | 1.43 | unsupported |
| WAMR AOT | 1.57 | 1.44 | 1.42 | unsupported |
| WasmEdge | 1.74 | 1.64 | 1.76 | unsupported |
| Wasmer | 2.08 | 2.02 | 2.03 | 1.33 |
| Wasmtime | 2.41 | 2.30 | 2.37 | 1.46 |
| Wazero | 4.72 | 4.77 | 4.71 | unsupported |
| Node | 7.95 | 8.05 | 8.25 | unsupported |
| Bun | 8.77 | 11.05 | 9.53 | unsupported |
lime1 和 simd128 单独用都不是魔法。有时有用,有时有害,有时差异被基准噪声淹没。
wide_arithmetic 不一样。在完整测试的稳定版里,只有 Wasmtime 和 Wasmer 能跑完整的 wide_arithmetic 构建;WAMR 以不支持的操作码 0xfc13 拒绝。而当它生效时,是整个实验里最大的加速:
- Wasmtime 46.0.0:不用该特性 2.41 倍原生,用后 1.46 倍原生。
- Wasmer 7.1.0:不使用时 2.08 倍原生,用后 1.33 倍原生。
这正是加密代码需要的改变。libsodium 里很多昂贵操作是算术密集型的,如果 WebAssembly ISA 能直接表达这些算术,运行时需要重新发现 C 编译器早已知道的东西就少得多。
失败案例
大多数运行顺利完成,但不是全部。
Bun 1.2.17 在 baseline 构建的 box_easy 上失败。Bun 1.1.16 在 lime1 和 lime1+simd128 构建的 pwhash_argon2i 上失败。Node 22.3.0 在 baseline、lime1 和 lime1+simd128 构建的 pwhash_argon2i、pwhash_argon2id、pwhash_scrypt 上失败。
Node 22.3.0 的密码哈希失败没法通过调大 Node 的 JavaScript 堆或栈设置解决,而是通过给 Wasm 模块显式的最大线性内存解决的。baseline 构建下,1024 页(64 MiB)的最大值让 pwhash_argon2i、pwhash_argon2id 和 pwhash_scrypt 跑完;pwhash_scrypt 在 512 页失败,在 1536 页及以上又段错误。这看起来是 V8 内存模式的阈值,而不是简单的“内存给多就好”。
WAMR 2.1.0(2024 版本)即使在 AOT 模式下也无法编译 baseline 模块。WAMR 2.3.1 和 2.4.4 能编译并运行 baseline、lime1、lime1+simd128,但跑不了 wide_arithmetic。
这些失败已从聚合中排除;报告的中位时间为零的行同样排除。
运行时在变快吗
一部分在变快。
Wasmtime 是最明确的“是”:在这个基准里逐年变快,幅度不大但持续。Node 也是“是”,只是斜率平缓。Bun 在 2025 到 2026 之间是响亮的“是”。Wazero 基本持平。WAMR 在能工作的版本之间也基本持平,但“持平”在约 1.4 到 1.6 倍原生已经是非常好的位置。Wasmer 只看 baseline 表现不一,但 2026 版本支持 wide_arithmetic 改变了加密代码的实际答案——开启该特性后,它是我能比较的当前正常版本里最快的完整 2026 结果。wasm2c 依然很好。WAVM 给出了最快的 2026 baseline 数字,但没有公平的 2024/2025 对照。WasmEdge 一旦强制走 AOT 模式,表现依然出色。
要点
在 WebAssembly 里跑 CPU 密集型加密任务,运行时选择仍然很重要。最快和最慢的当前结果差距很大:Wasmer 配 wide_arithmetic 是 1.33 倍原生,当前 Bun baseline 是 8.77 倍原生。
特性支持同样重要。当 WebAssembly 模块能用上更好的算术指令,同一个运行时可以从“还不错”变成“惊人地接近原生”。
令人欣慰的是主流运行时没有停滞:Wasmtime 稳步提升,Bun 大幅跃升,Wasmer 拿到了对真实加密工作负载很关键的特性。
不太令人欣慰的是 WebAssembly 性能仍然不是单一概念。它取决于运行时、版本、启用的 WebAssembly 特性、代码是否经 JavaScript 的 WASI 跑,以及是否允许提前原生编译。
所以,对自己实际的工作负载做基准测试。如果负载类似 libsodium,2026 年的答案大致是:WebAssembly 可以接近原生,wide_arithmetic 值得关注,而且确实有一些运行时在变快。