WebAssembly到底能不能让前端性能起飞?我试了试

开头

先说结论:能,但别指望它解决所有前端性能问题。

我最早接触 WebAssembly 是在 2017 年,当时各大浏览器的支持度还磕磕绊绊。看着那些 demo —— 浏览器里跑 Unity 游戏、实时视频处理,心里就痒痒:这玩意儿是不是能让 JavaScript 那点可怜的计算能力原地起飞?

但真正动手写起来,才发现事情没那么简单。今年趁项目间隙,我专门搭了个小实验:用 Rust 写了个 WebAssembly 模块,跑一个比较重的计算任务,跟纯 JavaScript 硬刚了一把。下面直接看代码和结果。

实验:计算斐波那契数列第 40 项

任务很俗,但对比明显。

JavaScript 版

function fibJS(n) {
  if (n <= 1) return n;
  return fibJS(n-1) + fibJS(n-2);
}

console.time('JS');
console.log(fibJS(40));
console.timeEnd('JS');

WebAssembly 版(Rust 编译)

首先,Rust 源码 src/lib.rs

#[no_mangle]
pub extern "C" fn fib_wasm(n: u32) -> u32 {
    if n <= 1 {
        return n;
    }
    fib_wasm(n - 1) + fib_wasm(n - 2)
}

编译成 wasm 后用 wasm-bindgen 生成胶水代码,然后在 JS 里调用:

import init, { fib_wasm } from './pkg/wasm_perf.js';

async function run() {
  await init();
  console.time('Wasm');
  console.log(fib_wasm(40));
  console.timeEnd('Wasm');
}
run();

结果(Chrome 118, MacBook M1 Pro)

实现耗时 (ms)
JS1032
Wasm342

快了大约 3 倍。而且随着 n 增大,差距还会拉大。

WebAssembly vs JavaScript performance chart

为什么 Wasm 更快?

简单说几个点:

  • 类型确定:wasm 的变量类型在编译时就是固定的,没有 JS 的动态类型和 JIT 优化的预热/去优化开销。
  • 内存管理直接:wasm 操作线性内存,没有 GC 干扰,循环里分配临时对象?不存在的。
  • 体积小:二进制格式,解析快。

但注意:这里我用了最朴素的递归,没做优化。实际项目中这种纯 CPU 密集任务其实不太常见,更常见的是对大量数据的处理,比如图像像素操作、物理引擎碰撞计算。那种场景下,Wasm 的缓存友好性也强很多。

一个反直觉的例子:字符串操作

我接着试了试字符串拼接和正则匹配。结果 Wasm 反而更慢,因为涉及 JS 和 Wasm 之间的边界数据传递。每次传字符串都要编解码,得不偿失。

// 字符串拼接 demo (Rust)
#[wasm_bindgen]
pub fn concat_strings(a: &str, b: &str) -> String {
    format!("{}{}", a, b)
}

调用时,JS 的字符串先被复制到 Wasm 内存,处理完再复制回来。对于几 MB 的大文本,开销就很大了。所以别想着用 Wasm 做 DOM 操作或大量字符串处理,那还是 JS 的天下。

到底什么场景适合上 Wasm?

根据我的实测和思考,总结了三个靠谱方向:

  1. 计算密集型算法:数值计算、加密、音视频编解码、物理引擎。比如市场上那些“浏览器里跑 FFmpeg”的项目,基本都是 Wasm 驱动的。
  2. 移植已有 C/C++/Rust 库:不想重写,就直接编译。比如 SQLite、libjpeg、opencv。但不能无脑用,要看接口复杂度。
  3. 游戏和图形渲染:Unity 小游戏,或者像 Google Earth 那种需要大量 3D 渲染的,Wasm 比 asm.js 更高效。

至于“替代 JavaScript”这种话,听听就好。Wasm 没有 GC,不能直接操作 DOM,生态也远不如 JS 成熟。它更像是个“性能协处理器”。

开发的真实体验

老实说,开发体验挺糟心的。调试麻烦,堆栈信息一团乱,没有 source map 时跟裸写汇编差不多。Rust 的 wasm 工具链还算好的,C++ 用 Emscripten 那套更是让人头大。

另外,包体积也要注意。一个最小的 Rust wasm 模块,光“Hello World”就有 2KB 左右,加上胶水代码和依赖,很容易膨胀到几十 KB。对于移动端,首屏加载还是得掂量掂量。

最后说点个人看法

我虽然踩了坑,但觉得 Wasm 确实是有用的工具。它让前端第一次拥有接近原生的计算能力。但前提是你清楚它的适用边界。别听人一吹“Wasm 性能起飞”就脑袋发热全上,先问自己:我要解决的问题是“计算密集型”吗?如果是,那就大胆用;如果不是,JS 的异步和事件驱动才是主流。

我后面还会再试 WebAssembly 的 SIMD 和多线程(Threads proposal),那时候估计又有新故事可以聊。

觉得内容不错?我要

评论 暂无评论
暂无评论,快来抢沙发吧~