Wasm 为 Web 开发带来无限可能

518 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

作者:于帆

定义

首先我们给它下个定义。

WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式,基于栈式虚拟机的二进制指令集,可以作为编程语言的编译目标,能够部署在 web 客户端和服务端的应用中。

相关概念

参考下图,计算机的主要架构如下。最底层是 CPU 的指令集,主要分为复杂指令集和简单指令集。

复杂指令集是 x86、x64(也叫 x86-64, amd64) 两种架构,专利在 Intel 和 AMD 两家公司手里, 该架构 CPU 主要是 Intel 和 AMD 两家公司,这种 CPU 常用在 PC 机上,包括 Windows,macOS 和 Linux。

简单指令集是 arm 一种架构,专利在 ARM 公司手里,该架构 CPU 主要有高通、三星、苹果、华为海思、联发科等公司。这种 CPU 常用在手机上,包括安卓和苹果。

指令集是什么呢?直接把阮一峰的老师的一个 例子 粘过来,大家可以看一下。

c 语言的源程序。

int add_a_and_b(int a, int b) {
  return a + b;
}
int main() {
  return add_a_and_b(2, 3);
}

所对应的汇编就是下边的样子。

_add_a_and_b:

push   %ebx

mov    %eax, [%esp+8] mov    %ebx, [%esp+12]

add    %eax, %ebx pop    %ebx ret  _main:

push   3

push   2

call   _add_a_and_b add    %esp, 8

ret

这里的 push mov 每一条指令就是指令集规定的内容,规定了操作码、操作数以及具体的功能。当然这里是用汇编表示的,主要是为了我们人类来读写,最终还会转成 0,1 序列。上边每个单词都会有一个数字相对应,比如 add 指令对应 00000011 。

通过规定的指令集(加法的指令,压栈指令等),编写相关程序,然后 CPU 就会一条一条的执行,最终实现相应的功能。

而 WebAssembly 就规定了一套指令集,更准确的来说是虚拟指令集,因为这套指令集是跑在虚拟机上的,而不是直接由硬件运行。

历史

上边我们知道了 WebAssembly 的 Assembly ,即汇编,也就是指令集。下边在回顾下 Web,即 WebAssembly诞生的原因。

这里就得谈到 javaScript 了,众所周知, javaScript 是一门动态类型的语言,编写程序时无需考虑变量类型,而且还可以运行时改变类型。对于我们开发者,确实很方便,但对于运行它的引擎就很有问题了。从下图,看一下 V8 引擎从 js 源码到执行的一个过程。

由于 js 的动态类型,解释器在执行代码的时候会在类型判断上带来一定的性能消耗,降低执行速度。所以 V8 引擎采用了 JIT(即时编译技术) 技术,监控一些经常执行的代码,将其编译成 CPU 直接执行的机器码,提高执行速度。但由于 js 动态类型,在某些情况下还得反优化,回到字节码进行执行。

随着前端的不断发展,项目的大小和复杂度不断增大,对于某些场景,性能上可能已经无法满足,浏览器厂商们也一直在探索性能优化的方法。

NaCl/PNaCl

2011 年 Google 在 Chrome 中使用了 NaCl 技术,可以使得 C 语言编写的程序运行到浏览器中,下边是维基百科 的定义。

Google Native Client(缩写为NaCl),是一个由谷歌所发起的开放源代码计划,采用BSD许可证。它采用沙盒技术,让Intel x86ARMMIPS子集的机器代码直接在沙盒上运行。它能够从浏览器直接运行程序机器代码,独立于用户的操作系统之外,使Web应用程序可以用接近于机器代码运作的速度来运行,同时兼顾安全性。其功能类似于微软ActiveX,但是ActiveX只支持视窗系统。

但一个完整的 NaCl 应用,在分发时需要提供支持多个架构平台(X86 / X64 / ARM 等)的模块文件,后来谷歌又推出了与底层架构无关的 PNaCl 技术。但由于其开发难度、兼容性等问题最终没有普及开来。在 2017 年 Google 宣布放弃 PNaCl 转向 WebAssembly。

ASM.js

ASM.js 是 Mozilla 在 2013 年推出的,是 javaScript 的一个严格子集,可以作为 C/C++ 编译的目标语言,从而使得 js 引擎可以采用 AOT(Ahead Of Time) 预先编译的编译策略,也就是在运行前直接编译成机器码,因此运行速度会有一定的提升。

ASM.js 通常不直接编写,而是作为一种通过编译器生成的中间语言,该编译器获取 C++ 或其他语言的源代码,然后输出 ASM.js。

例如下边的 C 语言代码。

int f(int i) {
  return i + 1;
}

经过编译器编译会生成下边的 js 代码。

function f(i) {
  i = i|0;
  return (i + 1)|0;
}

注意这里的|0 在 js 中相当于和 0 进行了或操作,所以不影响原本的逻辑。在 asm.js 中起到了类型标记的作用,这样 js 引擎执行的时候就知道 i 是一个整型,返回值是一个整型。除了或操作这种,ASM.js 标准中还规定了很多类似的标记规则,用于告诉 js 引擎变量的类型,便于进行 AOT 优化。

这看起来和 TypeScript 很像,但其实不是一种东西。TypeScript 是 js 的一个超集,浏览器并不能直接执行 ts,还需要转换为 js 去执行。ts 主要是帮助我们开发人员去看的,增加了代码的可读性,也可以让编辑器提前发现一些错误。而 asm.js 是用于引擎的编译优化。

WebAssembly

接下来看一下 WebAssembly 的历史。

2015 年 4 月,WebAssembly Community Group 成立;
2015 年 6 月,WebAssembly 第一次以 WCG 的官方名义向外界公布;
2016 年 8 月,WebAssembly 开始进入了漫长的 “Browser Preview” 阶段;
2017 年 2 月,WebAssembly 官方 LOGO 在 Github 上的众多讨论中被最终确定;同年同月,一个历史性的阶段,四大浏览器(FireFox、Chrome、Edge、WebKit)在 WebAssembly 的 MVP(最小可用版本)标准实现上达成共识,这意味着 WebAssembly 在其 MVP 标准上的 “Brower Preview” 阶段已经结束;
2017 年 8 月,W3C WebAssembly Working Group 成立,意味着 WebAssembly 正式成为 W3C 众多技术标准中的一员。

WebAssembly 于 2019 年 12 月 5 日成为万维网联盟(W3C)的推荐标准,与 HTML,CSS 和 JavaScript 一起成为 Web 的第四种语言。

可以看一下目前浏览器的支持程度,已经算比较高了。

WebAssembly 文本格式

实际上 WASM 是一堆可以直接执行二进制格式,但是为了易于在文本编辑器或开发者工具里面展示,WASM 也设计了一种 “中间态” 的文本格式,以 .wat 或 .wast 为扩展命名,然后通过 wabt 等工具,将文本格式下的 WASM 转为二进制格式的可执行代码,以 .wasm 为扩展的格式。

来看一段 WASM 文本格式下的模块代码:

(module
  (func $i (import "imports" "imported_func") (param i32))
  (func (export "exported_func")
    i32.const 42
    call $i
  )
)

上述代码逻辑如下:

  • 首先定义了一个 WASM 模块,然后从一个 imports JS 模块导入了一个函数 imported_func ,将其命名为 $i ,接收参数 i32
  • 然后导出一个名为 exported_func 的函数,可以从 Web App,如 JS 中导入这个函数使用
  • 接着为参数 i32 传入 42,然后调用函数 $i

初体验

mp.weixin.qq.com/s/p8_U4aVeJ…

Wasm工作方式

Wasm的工作方式是从服务器上加载后缀名为.wasm的代码文件,V8引擎中的wasm compiler将负责对其解码并编译为机器码,从而可以被浏览器所执行。

为什么快

本质上是因为wasm是一门“低级语言”,具体原因有如下几个:

  • 从网络上获取wasm代码比js代码要快,因为其代码更精简;
  • 解码wasm比解析js要更快;
  • 代码编译和优化要更快,因为wasm更接近机器码并且可以在服务器上提前做优化;
  • wasm是包含类型的,可以节约大量类型判断的代码和编码反复优化的问题;
  • wasm是面向目标机器的低级语言,所以指令设计上可以针对编译目标做优化。

总结及应用领域

  • 早期是为了解决前端代码在浏览器上运行的性能问题而实现的,现已经超脱最初目的,云原生,容器,Wasm的沙箱机制带来的隔离性和安全性,都比Docker做的更好。Docker的创始人也曾说过:"如果WASM + WASI[WebAssembly system interface(简称Wasi)]在2008年存在,我们就不需要创建Docker。
  • wasm不是框架或者库,所以它不是我们可以一行行码的代码,所以不能说它能做什么而是说被应用到哪些领域
    • web端的音频,视频编码解码
    • 第三方语言编写的web框架 eg:Rust库,C/C++库(轻量级库 eg:加解密库)
    • 图表和可视化数据的一些底层引擎可以用wasm重写,性能会得到极大的提高:重写d3.js的force引擎(力引导布局)
    • 云,轻量级的云原生管控
  • wasm的运行时可以时web浏览器但不只于浏览器
  • 保持关注,持续投入,未来可期