https://karnwong.me/posts/rss.xml

WASM FFI performance benchmark

2025-04-15

cover


WASM is known for being very portable because the only thing it asks for is a browser runtime. This means it doesn't matter if you have machines on linux/amd64, windows/amd64, darwin/aarch64, WASM can run on all these platforms without required modifications.

WASM can be used directly, or as an FFI through various languages/SDKs. One common pattern is when you have a multi-platform apps, the core logic would be compiled into WASM, which is then embedded within app implementations (which can be in Swift for iOS, Kotlin for Android, Electron for web/desktop).

I was curious about the overhead from running WASM through various languages, this is the results.

Setup

Given Rust-to-WASM has the best performance, the WASM binary used as FFI would be written in Rust, which then compiled into WASM.

The code is pretty simple: add random numbers to a fix-sized array, then sum the array, and divide it with the array length.

use rand::Rng;

fn main() {
    let mut vector: Vec<f64> = Vec::new();
    let loops: i32 = 999999;

    for i in 0..loops {
        let i_float64 = i as f64;
        let random_float = rand::rng().random_range(0.0..999999.0);

        vector.push(random_float * i_float64);
    }

    println!("Vector length: {:?}", vector.len());

    let vector_sum: f64 = vector.iter().sum();
    println!(
        "Vector sum divided by total loops: {:?}",
        vector_sum / loops as f64
    );
}

Then execute the WASM binary via wasmtime (native), go, python and rust's wasmtime sdk.

Raw Numbers

mean_smedian_smin_smax_slanguage
0.0652170.0648730.0604790.084518go
0.0491940.0488820.0460700.055017native
0.1083580.1073700.1020750.144279python
0.0659580.0653920.0607280.077792rust

We will revisit this later.

Benchmark Results

Statistics

overview

Python takes the longest to execute WASM binary, which is expected since Python is an interpreted language. Running WASM binary directly is also fastest, this makes sense because there's no languages/SDKs overhead.


go-vs-rust

Although it's very surprising to see that Go and Rust have almost similar performance, but max value for Go, which is slightly higher than Rust's makes sense due to its garbage collector.

Execution Time Throughout Iterations

iterations

This coincides with above statistics, and you can clearly see that Go and Rust are having almost identical performance.

Conclusion

Python has the most overhead for running WASM as FFI. Native WASM execution is fastest since there is no SDK overhead.

Go and Rust are slightly slower than Native (1.62% difference), but Go and Rust itself are having a 0.8% gap (0.000519 second difference).