Frontend Development 9 min read

Performance Comparison of WebAssembly (Rust) and JavaScript for Fibonacci and Convex Hull Algorithms

This article investigates whether WebAssembly truly outperforms JavaScript by compiling Rust to wasm and comparing execution times of Fibonacci and convex‑hull algorithms, revealing that data copying and serialization overhead can make wasm slower in realistic front‑end scenarios.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Performance Comparison of WebAssembly (Rust) and JavaScript for Fibonacci and Convex Hull Algorithms

The article questions the common claim that WebAssembly (wasm) is several times faster than JavaScript (js) by conducting concrete performance tests. It compiles Rust code to wasm, runs the same algorithms in pure js, and measures execution time in a browser environment.

Simple Fibonacci benchmark

Rust code compiled to wasm:

#[wasm_bindgen]
pub fn fib_wasm(value: u32) -> u32 {
    if value <= 1 {
        return value;
    }
    fib_wasm(value - 1) + fib_wasm(value - 2)
}

Equivalent JavaScript implementation:

function fibJs(value) {
  if (value <= 1) {
    return value;
  }
  return fibJs(value - 1) + fibJs(value - 2);
}

Test harness used to time both versions:

const maxIterTime = 10;
const testFib = (fib) => {
  const result = [];
  for (let i = 10; i < 10 + maxIterTime; i++) {
    result.push(fib(i));
  }
};
console.time("js");
testFib(fibJs);
console.timeEnd("js");
console.time("wasm");
testFib(fibWasm);
console.timeEnd("wasm");

The results show that the js implementation is indeed several times faster than the wasm version for this naïve recursive calculation, confirming the popular claim for this specific case.

Real‑world convex‑hull benchmark

The author then evaluates a more realistic scenario: computing the convex hull of a point set using the Graham‑scan algorithm. Rust implementation (compiled to wasm):

#[wasm_bindgen]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Vector { x: f64, y: f64 }

impl Sub<&Vector> for &Vector {
    type Output = Vector;
    fn sub(self, rhs: &Vector) -> Self::Output {
        Vector { x: self.x - rhs.x, y: self.y - rhs.y }
    }
}

fn cross_product(a: &Vector, b: &Vector) -> f64 { a.x * b.y - b.x * a.y }

pub fn graham_scan(mut array: Vec
) -> Vec
{
    if array.len() <= 3 { return array; }
    // find min‑y point
    let mut p0 = &array[0];
    for p in array.iter().skip(1) {
        if p.y < p0.y || (p.y == p0.y && p.x < p0.x) { p0 = p; }
    }
    let p0 = p0.clone();
    fn get_polar_angle(v: &Vector) -> f64 { v.y.atan2(v.x) }
    array.sort_by(|a, b| {
        let a_p0 = a - &p0
        let b_p0 = b - &p0
        let pa = get_polar_angle(&a_p0);
        let pb = get_polar_angle(&b_p0);
        match pa.partial_cmp(&pb).unwrap() {
            Ordering::Equal => {
                let da = a_p0.x.powf(2.) + a_p0.y.powf(2.);
                let db = b_p0.x.powf(2.) + b_p0.y.powf(2.);
                da.partial_cmp(&db).unwrap()
            }
            other => other,
        }
    });
    let mut result = vec![p0];
    for p in array.iter().skip(1) {
        loop {
            let len = result.len();
            if len >= 2 {
                let p1 = &result[len - 1];
                let p2 = &result[len - 2];
                if cross_product(&(p1 - p2), &(p - p1)).is_sign_negative() {
                    result.pop();
                    continue;
                }
            }
            break;
        }
        result.push(p.clone());
    }
    result
}

Corresponding JavaScript version:

type Vector = { x: number, y: number };
function grahamScan(array) {
  if (array.length <= 3) return array;
  let p0 = array[0];
  for (let i = 1; i < array.length; i++) {
    const p = array[i];
    if (p.y < p0.y || (p.y === p0.y && p.x < p0.x)) p0 = p;
  }
  function getPolarAngle(v) { return Math.atan2(v.y, v.x); }
  array.sort((a, b) => {
    const a_p0 = createVector(a, p0);
    const b_p0 = createVector(b, p0);
    const pa = getPolarAngle(a_p0);
    const pb = getPolarAngle(b_p0);
    if (pa === pb) {
      const da = a_p0.x ** 2 + a_p0.y ** 2;
      const db = b_p0.x ** 2 + b_p0.y ** 2;
      return da - db;
    }
    return pa - pb;
  });
  const result = [p0];
  for (let i = 1; i < array.length; i++) {
    const p = array[i];
    while (result.length >= 2) {
      const len = result.length;
      const p1 = result[len - 1];
      const p2 = result[len - 2];
      if (crossProduct(createVector(p1, p2), createVector(p, p1)) < 0) {
        result.pop();
        continue;
      }
      break;
    }
    result.push(p);
  }
  return result;
}
function createVector(a, b) { return { x: a.x - b.x, y: a.y - b.y }; }
function crossProduct(a, b) { return a.x * b.y - a.y * b.x; }

The test data (copied from LeetCode) consists of a list of 2‑D points. After running both implementations, the author observes that the wasm version is several times slower than the js version. The primary cause is the overhead of copying and deserializing data between the JavaScript and wasm memory spaces.

Rust wasm wrapper illustrating the data conversion:

#[wasm_bindgen]
pub fn graham_scan_wasm(array: Vec
) -> Vec
{
    if array.len() <= 3 { return array; }
    let array: Vec
= array.into_iter()
        .map(|v| from_value::<[f64; 2]>(v).unwrap())
        .map(|v| Vector { x: v[0], y: v[1] })
        .collect();
    graham_scan(array)
        .into_iter()
        .map(|v| to_value(&v).unwrap())
        .collect()
}

And the corresponding JavaScript conversion step:

data.map(v => ({ x: v[0], y: v[1] }));

In the concluding section, the author notes that while wasm can be faster for pure computation, real‑world front‑end use cases inevitably involve data transfer between js and wasm, which can negate the performance advantage. This overhead is currently a key limitation of wasm in web applications.

Overall, the article provides a practical, code‑driven comparison that helps developers understand when wasm offers real benefits and when the cost of data marshaling may outweigh those gains.

frontendperformanceJavaScriptRustWasmwebassemblybenchmark
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.