Wasm 真的比 Js 快吗?

网上各种介绍wasm的文章都在说wasm的性能是js的数倍之多,实际情况真的是这样吗?

本文用rust编译到wasmjs进行性能对比,尝试解答这个问题

普通计算

直接上代码,如果是计算斐波那契数列

rust 代码如下

rust 复制代码
#[wasm_bindgen]
pub fn fib_wasm(value: u32) -> u32 {
    if value <= 1 {
        return value;
    }

    fib_wasm(value - 1) + fib_wasm(value - 2)
}

运行 wasm-pack build --verbose --release --target web 编译成 js

相同代码用 js 编写,代码如下所示

typescript 复制代码
function fibJs(value: number): number {
  if (value <= 1) {
    return value
  }
  return fibJs(value - 1) + fibJs(value - 2)
}

测试代码如下

javascript 复制代码
const maxIterTime = 10

const testFib = (fib: (value: number) => number) => {
  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")

对比结果如下

可以看到js的耗时确实是wasm的数倍之多, 但是由此就能得到结论wasmjs快吗?

现实场景

考虑一个真实的场景,计算凸包, 因为笔者工作原因,最近有用到该算法,就拿该算法来对比

本文不讨论该算法的细节,后面会出一篇文章专门来介绍该算法的实现

编写一个 graham_scan 算法, 核心的rust 代码如下

rust 复制代码
#[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<Vector>) -> Vec<Vector> {
    if array.len() <= 3 {
        return array;
    }

    let mut p0 = &array[0];

    // find min y point assign p0
    for p in array.iter().skip(1) {
        if p.y < p0.y {
            p0 = p;
        } else if 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 polar_angle_a = get_polar_angle(&a_p0);
        let polar_angle_b = get_polar_angle(&b_p0);
        match polar_angle_a.partial_cmp(&polar_angle_b).unwrap() {
            Ordering::Equal => {
                let dist_a = a_p0.x.powf(2.) + a_p0.y.powf(2.);
                let dist_b = b_p0.x.powf(2.) + b_p0.y.powf(2.);
                (dist_a).partial_cmp(&dist_b).unwrap()
            }
            v => v,
        }
    });

    let mut result: Vec<Vector> = 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
}

相同的js代码如下

typescript 复制代码
type Vector = {
  x: number
  y: number
}

function grahamScan(array: { x: number; y: number }[]) {
  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) {
      p0 = p
    } else if (p.y == p0.y && p.x < p0.x) {
      p0 = p
    }
  }

  function get_polar_angle(v: { x: number; y: number }) {
    return Math.atan2(v.y, v.x)
  }

  array.sort((a, b) => {
    const a_p0 = createVector(a, p0)
    const b_p0 = createVector(b, p0)
    const polar_angle_a = get_polar_angle(a_p0)
    const polar_angle_b = get_polar_angle(b_p0)
    if (polar_angle_a == polar_angle_b) {
      const dist_a = a_p0.x ** 2 + a_p0.y ** 2
      const dist_b = b_p0.x ** 2 + b_p0.y ** 2
      return dist_a - dist_b
    }
    return polar_angle_a - polar_angle_b
  })

  const result = [p0]

  for (let i = 1; i < array.length; i++) {
    const p = array[i]
    while (result.length >= 2) {
      const length = result.length
      const p1 = result[length - 1]
      const p2 = result[length - 2]
      if (crossProduct(createVector(p1, p2), createVector(p, p1)) < 0) {
        result.pop()
        continue
      }
      break
    }
    result.push(p)
  }
  return result
}

function createVector(a: Vector, b: Vector) {
  return {
    x: a.x - b.x,
    y: a.y - b.y,
  }
}

function crossProduct(a: Vector, b: Vector) {
  return a.x * b.y - a.y * b.x
}

测试用的样本文件, 该样本是从leetcode上拷贝下来的

javascript 复制代码
const data = [
  [0, 2],[0, 4],[0, 5],[0, 9],[2, 1],[2, 2],[2, 3],[2, 5],[3, 1],[3, 2],[3, 6],[3, 9],[4, 2],[4, 5],[5, 8],[5, 9],[6, 3],[7, 9],[8, 1],[8, 2],[8, 5],[8, 7],[9, 0],[9, 1],[9, 6],
]

对比结果如下

这个时候wasm用时居然是js的数倍之多,核心原因就是花费了太多的时间在数据的拷贝和反序列化

rust 复制代码
// rust 的 wasm wrapper
#[wasm_bindgen]
pub fn graham_scan_wasm(array: Vec<JsValue>) -> Vec<JsValue> {
    if array.len() <= 3 {
        return array;
    }

    let array: Vec<Vector> = 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()
}
javascript 复制代码
// js 也需要做转换
data.map((v) => ({ x: v[0], y: v[1] }))

或许这样对比对 wasm 不公平

但是wasm本身不具备操作dom的能力,几乎所有的输入数据(用户交互产生的数据,网络请求的数据)都是要从js那边拷贝过来,然后在wasm里面计算,最后再拷贝给js展现, 这才是现实场景的应用,所以数据的拷贝在所难免,一不小心就会导致wasm变慢,这也是目前wasm的一个问题所在

如果大家有什么想法,欢迎在评论区友好讨论。谢谢大家

相关推荐
weixin_307779138 小时前
一维无粘 Burgers 方程的激波形成问题:MacCormack 格式求解
c++·算法·matlab
HugoStudio_SWAN8 小时前
洛谷 B4500 / B4449 / B3843 凯撒密码、密码强度与密码合规——加密与安全的三道门
c++·学习·程序人生·算法·安全
枫叶林FYL9 小时前
【群体智能集群控制工程实践】第3章 一致性协同算法
算法
剑之所向10 小时前
.NET 官方`System.Threading.Channels`
前端·javascript·数据库
禹凕10 小时前
滑动窗口算法实战指南
python·算法
DisonTangor10 小时前
【腾讯混元雪耻归来】 Hy4 preview:770B 参数 MoE 旗舰模型,1M 上下文全面开源
人工智能·算法·开源·aigc·腾讯云·腾讯云ai代码助手
明月_清风11 小时前
从二叉树到 B+ 树:一文搞懂工程中「树」的演化之道
数据结构·算法·go
渡我白衣11 小时前
并查集:基础认识与模拟实现
android·java·javascript·数据结构·c++·算法·并查集
hetao173383711 小时前
2026-09-01~09-04 hetao1733837 的刷题记录
c++·算法
Evand J12 小时前
【MATLAB例程,图像滤波5】 反谐波均值滑动窗口滤波(CHMF)图像降噪与质量评价,附代码下载链接
图像处理·算法·计算机视觉·matlab·均值算法·滑动窗口滤波·均值滑动