Performance of WebAssembly runtimes in 2026

Lobsters Hottest Tools

Summary

This article benchmarks the performance of various WebAssembly runtimes (WAVM, WasmEdge, WAMR, wasm2c, Wasmer, Wasmtime, Wazero, Node, Bun) using the libsodium crypto library, comparing versions from 2024, 2025, and 2026. Results show that WAVM, WasmEdge (with AOT), WAMR (AOT), wasm2c, Wasmer, and Wasmtime achieve near-native performance for CPU-bound crypto, while wide_arithmetic instructions benefit crypto code.

<p><a href="https://lobste.rs/s/fhmvsf/performance_webassembly_runtimes_2026">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 06/23/26, 05:48 PM

# Performance of WebAssembly runtimes in 2026 Source: [https://00f.net/2026/06/23/webassembly-runtimes-2026/](https://00f.net/2026/06/23/webassembly-runtimes-2026/) I wanted to know if WebAssembly runtimes are getting faster\. This is a follow\-up to the earlier libsodium WebAssembly benchmarks from[2019](https://00f.net/2019/04/09/benchmarking-webassembly-using-libsodium/),[2021](https://00f.net/2021/02/22/webassembly-runtimes-benchmarks/)and[2023](https://00f.net/2023/01/04/webassembly-benchmark-2023/)\. Not “does the newest version beat native code in one microbenchmark?”, and not “which runtime has the prettiest benchmark chart?”, but something more boring and more useful: If I take the same C crypto code, compile it to WebAssembly, and run it on the latest runtime, a runtime from one year ago, and a runtime from two years ago, are things actually improving? So I benchmarked libsodium on WebAssembly runtimes released around June 2024, June 2025, and June 2026\. The short version: - `WAVM`and`WasmEdge`can be very fast\.`WasmEdge 0\.17\.0`needed an explicit`\-\-run\-mode=aot`; without it, the compiled modules ran like interpreter\-mode Wasm\. - `WAMR`in AOT mode is also very fast, landing right next to`WAVM`and the best`Wasmtime`results\. - `wasm2c`,`Wasmer`, and`Wasmtime`are all close enough to native to be interesting for CPU\-bound crypto\. - `Wazero`is slower, but stable\. - The`Node`and`Bun`rows need a full rerun with longer benchmark loops\. A smoke test showed that the short\-loop run substantially under\-warmed the JITs\. - The experimental WebAssembly`wide\_arithmetic`instructions are a big deal for crypto code when runtimes support them\. ## What I measured The test program is libsodium’s benchmark suite, built from libsodium commit`8e3be8615ba6adcd7babaecf5e76f516890ba5fb`\. I built one native baseline and several WebAssembly variants: - native x86\-64, compiled with Zig using the local CPU target - plain WebAssembly - WebAssembly with`lime1` - WebAssembly with`lime1`and`simd128` - WebAssembly with`lime1`,`simd128`, and`wide\_arithmetic` For the native reference, libsodium was built with`\-Dcpu=native`\. For`wasm2c`, the generated C was compiled with`zig cc \-O3 \-march=native`\. For`WAMR`, I used AOT mode:`wamrc`compiled each`\.wasm`file to an`\.aot`file, and`iwasm`ran the resulting AOT file\.`wamrc`doesn’t accept`\-\-cpu=native`, so I used`\-\-target=x86\_64 \-\-cpu=x86\-64\-v4 \-\-opt\-level=3`, which matches the host’s available x86\-64 feature level and works across the WAMR versions that could compile these modules\. The native command was: ``` zig build -Denable_benchmarks -Doptimize=ReleaseFast -Dcpu=native -Diterations=3 ``` The WebAssembly commands were the same shape, with a`wasm32\-wasi`target and the feature\-specific CPU strings: ``` 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 ``` The host was an AMD Ryzen AI 9 HX 470 with 12 cores and 24 threads\. CPU boost was disabled and the maximum CPU frequency was 2 GHz\. The OS was Linux 7\.1\.0\-rc7, and Zig was`0\.17\.0\-dev\.948\+e949341b7`\. The numbers below are the geometric mean of per\-benchmark slowdowns relative to the native build\. Lower is better\. A value of`2\.0`means “twice as slow as native” on this machine\. I used`ITERATIONS=3`, so the very small libsodium tests are noisy and quantized\. Rows reporting zero time were excluded from the aggregate\. I did not pin benchmark processes to specific cores\. This is still useful for comparing broad runtime behavior, but don’t treat the last decimal place as meaningful\. ## Versions For every runtime except`WAVM`, I used the latest stable release available on June 23, 2026, plus a stable release from roughly one year earlier and one from roughly two years earlier\. Runtime202420252026`Bun`[`1\.1\.16`](https://github.com/oven-sh/bun/releases/tag/bun-v1.1.16)[`1\.2\.17`](https://github.com/oven-sh/bun/releases/tag/bun-v1.2.17)[`1\.3\.14`](https://github.com/oven-sh/bun/releases/tag/bun-v1.3.14)`Node`[`22\.3\.0`](https://nodejs.org/dist/v22.3.0/)[`24\.2\.0`](https://nodejs.org/dist/v24.2.0/)[`26\.3\.1`](https://nodejs.org/dist/v26.3.1/)`WAMR`[`2\.1\.0`](https://github.com/bytecodealliance/wasm-micro-runtime/releases/tag/WAMR-2.1.0)[`2\.3\.1`](https://github.com/bytecodealliance/wasm-micro-runtime/releases/tag/WAMR-2.3.1)[`2\.4\.4`](https://github.com/bytecodealliance/wasm-micro-runtime/releases/tag/WAMR-2.4.4)`WABT wasm2c`[`1\.0\.35`](https://github.com/WebAssembly/wabt/releases/tag/1.0.35)[`1\.0\.37`](https://github.com/WebAssembly/wabt/releases/tag/1.0.37)[`1\.0\.41`](https://github.com/WebAssembly/wabt/releases/tag/1.0.41)`WasmEdge`[`0\.14\.0`](https://github.com/WasmEdge/WasmEdge/releases/tag/0.14.0)[`0\.14\.1`](https://github.com/WasmEdge/WasmEdge/releases/tag/0.14.1)[`0\.17\.0`](https://github.com/WasmEdge/WasmEdge/releases/tag/0.17.0)`Wasmer`[`4\.3\.2`](https://github.com/wasmerio/wasmer/releases/tag/v4.3.2)[`6\.0\.1`](https://github.com/wasmerio/wasmer/releases/tag/v6.0.1)[`7\.1\.0`](https://github.com/wasmerio/wasmer/releases/tag/v7.1.0)`Wasmtime`[`22\.0\.0`](https://github.com/bytecodealliance/wasmtime/releases/tag/v22.0.0)[`34\.0\.0`](https://github.com/bytecodealliance/wasmtime/releases/tag/v34.0.0)[`46\.0\.0`](https://github.com/bytecodealliance/wasmtime/releases/tag/v46.0.0)`WAVM``n/a``n/a`[`nightly/2026\-04\-05`](https://github.com/WAVM/WAVM/releases/tag/nightly/2026-04-05)`Wazero`[`1\.7\.3`](https://github.com/wazero/wazero/releases/tag/v1.7.3)[`1\.9\.0`](https://github.com/wazero/wazero/releases/tag/v1.9.0)[`1\.12\.0`](https://github.com/wazero/wazero/releases/tag/v1.12.0)`WAVM`is awkward to compare historically\. The old available nightly collapsed to a 2022 binary for both the 2024 and 2025 slots, and that binary refused to run on this machine\. I only kept the 2026 nightly\. `WAMR 2\.1\.0`, the selected 2024 release, installed fine but its AOT compiler failed on these Zig\-generated modules with`invalid WASM stack data type`\. I kept the version in the matrix, but did not include an aggregate for it\. ## Baseline WebAssembly This is the plain WebAssembly build, without`lime1`, SIMD, or wide arithmetic\. Runtime202420252026`WAVM`n/an/a1\.41`WAMR AOT`n/a1\.591\.57`WasmEdge`1\.661\.981\.74`wasm2c`2\.012\.081\.86`Wasmer`2\.132\.562\.08`Wasmtime`2\.672\.542\.41`Wazero`4\.844\.704\.72`Node`8\.608\.227\.95`Bun`27\.4126\.428\.77There isn’t one universal trend\. `Wasmtime`steadily improved: 2\.67x native in 2024, 2\.54x in 2025, 2\.41x in 2026\. That’s not a revolution, but it is real progress\. `Node`also improved slowly, from 8\.60x native to 7\.95x native\. `Wazero`was basically flat: 4\.84x, 4\.70x, 4\.72x\. That’s not bad, but this benchmark doesn’t show a big speedup over the last two years\. `WAMR`in AOT mode was already fast in 2025 and slightly faster in 2026: 1\.59x native, then 1\.57x native\. I don’t have a complete 2024 WAMR number because`WAMR 2\.1\.0`couldn’t compile these modules\. `Wasmer`regressed in the 2025 release I tested, then recovered in 2026\. The 2026 baseline is slightly faster than the 2024 baseline, but not by much\. `wasm2c`improved modestly in 2026\. It remains one of the best options if ahead\-of\-time translation to native C is acceptable for your deployment model\. `Bun`is the outlier\. Its 2024 and 2025 results were far behind, but the 2026 result is about three times faster than the 2025 result\. It is still slower than`Node`on this benchmark, but the direction is excellent\. `WasmEdge`is fast too, but its command\-line behavior changed enough to matter\. My first`0\.17\.0`run accidentally used interpreter mode for compiled modules and looked catastrophically slow\. Running the compiled modules with`\-\-run\-mode=aot`fixed it: the 2026 baseline was 1\.74x native, between the 2024 and 2025 baseline results\. ## Best supported build by year The baseline table is useful because it compares the same WebAssembly target everywhere\. But if you are choosing a runtime for your own deployment, you probably care about the fastest build that runtime can actually run\. So for each runtime and year, I also selected the best complete result among the supported builds: baseline,`lime1`,`lime1\+simd128`, and`lime1\+simd128\+wide\_arithmetic`\. Runtime2024 best2025 best2026 best`WAVM`n/an/a1\.41 \(baseline\)`WAMR AOT`n/a1\.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\)Ranked by the best supported build, the complete current\-year results are: 2026 rankRuntimeBest buildSlowdown vs native1`Wasmer``lime1\+simd128\+wide\_arithmetic`1\.332`WAVM`baseline1\.413`WAMR AOT``lime1\+simd128`1\.424`Wasmtime``lime1\+simd128\+wide\_arithmetic`1\.465`WasmEdge``lime1`1\.646`wasm2c`baseline1\.867`Wazero``lime1\+simd128`4\.718`Node`baseline7\.959`Bun`baseline8\.77## CPU feature variants The WebAssembly feature story is more interesting than the year\-to\-year runtime story\. For the 2026 releases, these were the aggregate slowdowns: Runtimebaseline`lime1``lime1\+simd128``lime1\+simd128\+wide\_arithmetic``WAVM`1\.411\.591\.43unsupported`WAMR AOT`1\.571\.441\.42unsupported`WasmEdge`1\.741\.641\.76unsupported`Wasmer`2\.082\.022\.031\.33`Wasmtime`2\.412\.302\.371\.46`Wazero`4\.724\.774\.71unsupported`Node`7\.958\.058\.25unsupported`Bun`8\.7711\.059\.53unsupported`lime1`and`simd128`alone are not magic here\. Sometimes they help, sometimes they hurt, and sometimes the difference is lost in benchmark noise\. `wide\_arithmetic`is different\. Only`Wasmtime`and`Wasmer`could run the full`wide\_arithmetic`build among the complete stable rows I tested\.`WAMR`rejected it with unsupported opcode`0xfc13`\. But when`wide\_arithmetic`worked, it was the biggest speedup in the whole experiment: - `Wasmtime 46\.0\.0`: 2\.41x native without it, 1\.46x native with it\. - `Wasmer 7\.1\.0`: 2\.08x native without it, 1\.33x native with it\. That’s the kind of change cryptographic code wants\. A lot of libsodium’s expensive operations are arithmetic\-heavy\. If the WebAssembly ISA can express that arithmetic directly, the runtime has much less work to rediscover what the C compiler already knew\. ## Failures Most runs completed cleanly, but not all of them\. `Bun 1\.2\.17`failed`box\_easy`in the baseline build\.`Bun 1\.1\.16`failed`pwhash\_argon2i`in the`lime1`and`lime1\+simd128`builds\.`Node 22\.3\.0`failed`pwhash\_argon2i`,`pwhash\_argon2id`, and`pwhash\_scrypt`in the baseline,`lime1`, and`lime1\+simd128`builds\. The`Node 22\.3\.0`password\-hashing failures were not fixed by increasing Node’s JavaScript heap or stack settings\. They were fixed by giving the Wasm modules an explicit maximum linear memory\. With the baseline build, a 1024\-page maximum, or 64 MiB, made`pwhash\_argon2i`,`pwhash\_argon2id`, and`pwhash\_scrypt`complete\.`pwhash\_scrypt`failed with 512 pages and segfaulted again at 1536 pages and above, so this appears to be a V8 memory\-mode threshold rather than a simple “more memory is better” setting\. `WAMR 2\.1\.0`, the 2024 slot, could not compile even the baseline modules in AOT mode\.`WAMR 2\.3\.1`and`2\.4\.4`compiled and ran the baseline,`lime1`, and`lime1\+simd128`builds, but not`wide\_arithmetic`\. Those failures were excluded from the aggregate\. So were benchmark rows with a zero reported median\. ## So, are runtimes getting faster? Some of them are\. `Wasmtime`is the cleanest yes: it got faster every year in this benchmark\. Not massively faster, but consistently faster\. `Node`is also a yes, but the slope is gentle\. `Bun`is a loud yes between 2025 and 2026\. It still has a lot of ground to cover for this workload, but the improvement is too large to ignore\. `Wazero`is mostly flat\. `WAMR`is also mostly flat between the versions that worked here, but “flat” at about 1\.4x to 1\.6x native is a very good place to be\. `Wasmer`is mixed if you only look at the baseline, but the 2026 release supporting`wide\_arithmetic`changes the practical answer for crypto code\. With that feature enabled, it was the fastest complete 2026 result I could compare across a normal current release\. `wasm2c`remains good\. If you can translate WebAssembly to C ahead of time and compile it for the host, it is hard to beat\. `WAVM`produced the fastest 2026 baseline number, but I don’t have a fair 2024 or 2025 comparison\. `WasmEdge`remains excellent once it is forced into AOT mode\. The accidental interpreter\-mode run was a good reminder that command\-line defaults are part of the benchmark, too\. ## Takeaways If you run CPU\-heavy cryptography in WebAssembly, runtime choice still matters a lot\. The spread between the fastest complete current result and the slowest current result is large:`Wasmer`with`wide\_arithmetic`was 1\.33x native, while current`Bun`baseline was 8\.77x native\. Feature support matters too\. The same runtime can move from “pretty good” to “surprisingly close to native” when the WebAssembly module can use better arithmetic instructions\. The comforting part is that the mainstream runtimes are not standing still\.`Wasmtime`improved steadily\.`Bun`made a huge jump\.`Wasmer`gained a feature that matters for real crypto workloads\.`WasmEdge`remained fast once the AOT run mode was explicit\. The less comforting part is that WebAssembly performance is still not one thing\. It depends on the runtime, the release, the enabled WebAssembly features, whether the code goes through WASI from JavaScript, and whether ahead\-of\-time native compilation is allowed\. So benchmark your actual workload\. But if your workload looks like libsodium, the answer in 2026 is: WebAssembly can be close to native,`wide\_arithmetic`is worth caring about, and yes, some runtimes really are getting faster\.

Similar Articles

The best WebAssembly runtime may still be no runtime at all

Lobsters Hottest

The article benchmarks the WebAssembly-to-C approach using wasm2c with wide arithmetic support, demonstrating that it remains competitive with dedicated WebAssembly runtimes like Wasmer and Wasmtime in both speed and memory usage.

Evaluating Spec CPU2026

Hacker News Top

An in-depth evaluation of the new SPEC CPU2026 benchmark suite, which replaces SPEC CPU2017 with 52 workloads and a slower reference system (Ampere eMAG 8180), showing performance comparisons between modern CPUs.