WebAssembly 堪称一个革命性的技术,它给 Web 带来了更多的可能性,对于 CPU 密集型的应用,比如图片、音频、视频、直播、机器学习、AR、VR、游戏、在线会议、在线文档、在线 IDE、在线游戏等等,WebAssembly 既可以帮助 Web 突破性能瓶颈,也可以让 Web 得以利用其他语言的代码库。随着越来越多业务跟服务转战云端,基于 Web 本身优势,外有 WebAssembly 技术性能加持,在可预见的未来,WebAssembly 可让 Web 再次绽放。

WebAssembly 历史起源

WebAssembly 起源于 Mozilla 员工的一个业余项目。2010 年,在 Mozilla 从事 Android Firefox 开发的 Alon Zakai,为了把他以前开发的游戏引擎移植到浏览器上运行,利用业余时间开发了一款名叫 Emscripten 的编译器,可以把 C++ 代码通过 LLVM IR 编译成 JavaScript 代码。

到了 2011 年底,Emscripten 甚至能够成功编译 Python 和 Doom 等大型 C++ 项目,Mozilla 此时觉得这个项目很有前途,于是成立团队并邀请 Alon 全职开发这个项目。2013 年 Alon 和其他成员一起提出了 asm.js 规范,asm.js 是 JavaScript 语言的一个严格子集,试图通过“减少动态特性”和”添加类型提示“的方式帮助浏览器提升 JavaScript 优化空间。相较于完整的 JavaScript 语言,裁剪后的 asm.js 更靠近底层,更适合作为编译器目标语言。

asm.js 只提供两种数据类型:32 位带符号整数,64 位带符号浮点数,其他数据类型比如字符串、布尔值或者对象,asm.js 一概不提供,它们都是以数值的形式存在,保存在内存中,通过 TypedArray 调用。类型的声明也有固定写法:变量 | 0 表示整数,+变量 表示浮点数。例如下面一段代码:

1
2
3
4
5
6
7
8
9
function MyAsmModule() {
"use asm"; // 告诉浏览器这是个 asm.js 模块
function add(x, y) {
x = x | 0; // 变量 | 0 表示整数
y = y | 0;
return (x + y) | 0;
}
return { add: add };
}

支持 asm.js 的引擎提前识别出了类型,可以进行激进的 JIT(即时编译)优化,甚至是 AOT(事先编译)编译,大幅提升性能。不支持 asm.js 按普通 JavaScript 代码执行也不会影响运行结果。

但是 asm.js 的缺点也很明显,那就是“底层”得不够彻底,例如代码仍然是文本格式;代码编写仍然受 JavaScript 语法限制;浏览器仍然需要完成解析脚本、解释执行、收集性能指标、JIT 编译等一系列步骤。如果采用像 Java 类文件那样的二进制格式,不仅能缩小文件体积,减少网络传输时间和解析时间,还能选用更接近机器的字节码,这样 AOT/JIT 编译器实现起来会更轻松,效果也更好。

与此同时,Google 的 Chrome 团队也在试图解决 JavaScript 性能问题,但方向有所不同。Chrome 给出的解决方案是 NaCl(Google Native Client)和 PNaCl(Portable NaCl)。通过 NaCl/PNaC1,Chrome 浏览器可以在沙箱环境中直接执行本地代码。

asm.js 和 NaCl/PNaC1 技术各有优缺点,二者可以取长补短。Mozilla 和 Google 也看到了这一点,所以从 2013 年开始,两个团队就经常交流和合作。后来他们决定结合两个项目的长处,合作开发一种基于字节码的技术。到了 2015 年,“WebAssembly” 确定为正式名称并对外公开,W3C 成立了 WASM 社区小组(成员包括 Chrome、Edge、Firefox 和 WebKit),致力于推动 WASM 技术的发展。

  • 2016 年 Rust 1.14 发布,开始支持 WASM。
  • 2017 年 Google 决定放弃 PNaCl 技术;四大浏览器 Chrome、Edge、Safari、Firefox 更新版本开始支持 WASM。
  • 2018 年 Go 1.11 发布,开始支持 WASM。
  • 2019 年 Emscripten 更新为默认使用 LLVM 编译为 WASM 代码,停止对 asm.js 的支持;WebAssembly 成为万维网联盟(W3C)的推荐标准,与 HTML,CSS 和 JavaScript 一起成为 Web 的第四种语言。
  • 2021 年 1 月 6 日,Wasmer(🚀支持 WASI 和 Emscripten 的领先 WebAssembly 运行时) 1.0.0 版本发布。
  • 2022 年 09 月 20,Wasmtime(WebAssembly 的快速安全运行时) 的官方 1.0 版本发布。

全面认识 WebAssembly

WebAssembly 诞生背景

Web 平台的成熟催生了复杂而苛刻的网络应用,如交互式三维可视化,音频和视频软件,以及游戏。随着伴随着这一点,Web 上代码的效率和安全性变得比以往更加重要。然而,作为唯一的网络内置语言——JavaScript 并没有很好地满足这些要求,特别是作为编译目标。来自四个主要浏览器供应商的工程师们迎接挑战,合作设计了一个可移植的低级字节码,称为 WebAssembly 。它提供了紧凑的表示、高效的验证和编译,以及安全的低至无开销的执行。WebAssembly 不是致力于一个特定的编程模型,而是 WebAssembly 是对现代硬件的抽象,使其与语言、硬件和平台无关,其使用范围不只是 Web 浏览器;就目前而言,Node.js、Deno、 WebAssembly 运行时 等环境都可使用。

WebAssembly 是什么?

WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C / C ++ 等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。WebAssembly 是可移植体积小加载快并且兼容 Web 浏览器的全新类汇编语言格式,其具有高效安全开放标准等特性。

WebAssembly (缩写为Wasm)是基于堆栈的虚拟机的二进制指令格式。其设计目的不是为手写代码,而是为诸如 C、C++、Rust 等低级语言提供一个高效的、可移植的编译目标,支持在 Web 上部署客户端和服务器应用程序。

WebAssembly 试图取代 JavaScript 吗?

不会。在 WebAssembly 常见问题 中,作者给出了明确说明:“WebAssembly 旨在补充而非替代 JavaScript。随着时间的推移,WebAssembly 将允许许多语言被编译到 Web,而 JavaScript 具有令人难以置信的势头,并将继续保持 Web 的单一、特权( 如上所述 )动态语言。此外,预计 JavaScript 和 WebAssembly 将在许多配置中一起使用”。

理解 WebAssembly 文件格式

编译器 VS 解释器

  • 源代码(source code):由开发者基于 JavaScript、TypeScript 等编程语言直接提供,主要用来给人类阅读、维护的文本格式。即便是压缩、混淆后,依然是文本格式。
  • AST:抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法,并不会表示出真实语法中出现的每个细节。
  • 汇编代码:(Assembly Code),是介于高级语言和机器码之间的一种中间语言(也是人看的懂的语言)。它比机器代码高一级,比高级语言低一级。而且,它的语法类似于英语,但比高级编程语言更难。它经过转译最终也会成为机器码,所以抽象上来说,它和字节码是同一级别的。可以简单将其理解为机器语言的助记符,便于人们理解和记忆。像用地址符号(Symbol)或标号(Label)代替指令或操作数的地址,常见的 x86 汇编指令有 MOV、PUSH、POP 等。
  • 字节码:(Byte Code)是一种包含 执行程序、由一序列 OP 代码(操作码)/数据对 组成的二进制文件。 字节码是一种 中间码,它比机器码更抽象,需要 直译器转译 后才能成为机器码的中间代码(通常不是人类可读的语言)。
  • 机器码:(Machine Code),学名 机器语言指令,有时也被称为 原生码(Native Code),是电脑的 CPU 可直接解读 的数据(即计算机只认识 0 和 1)。
  • WebAssembly 文本格式 :推荐扩展名是 .wat(WebAssembly Text Format),是另外一种输出格式,是使用 “S- 表达式” 的文本格式,可以近似理解为与二进制等价的汇编代码。部分浏览器的开发者工具支持将 WASM 转换成 WAT 查看,便于在线调试。社区提供了 wasm2watwat2wasm 等成熟的工具将二者进行转换,可以在 WebAssembly/wabt (WebAssembly Binary Toolkit) 工具集中找到,所以也是可以直接编写 WAT 再转换成 WASM。
  • WebAssembly 二进制格式.wasm) :WebAssembly 的最终形式,是一种低级字节码,基于 16 进制表示;无需解析,只要解码并且检查确认代码,即可编译为机器码
  • WASI:(WebAssembly System Interface),一种与不同操作系统通信统一标准;它是为 WASM 专门设计一套引擎无关(engine-indepent)、面向非 Web 系统(non-Web system-oriented)的 API 标准;遵循可移植性、安全性两大原则。WASI 在 WASM 字节码与虚拟机之间,增加了一层“系统调用抽象层”。

WebAssembly 优缺点

从 WebAssembly 诞生背景及目标来看,它所要解决的是 JavaScript 语言本身限制,不足以应对 CPU 密集型应用的问题。因此,优缺点比较对象是 JavaScript、以及 Native Client 和 asm.js 等尝试“解决 Web 上安全快速代码”的方案。

Native Client:是一个沙箱,用于在浏览器中高效、安全地运行已编译的 C 和 C++ 代码,独立于用户的操作系统。它已于 2020 年弃用,支持将于 2021 年 6 月结束。
Asm.js:代码在很多方面类似于 C,但它仍然是可以在所有当前引擎中运行的完全有效的 JavaScript。Firefox 是目前唯一可以做到这一点的浏览器。已被弃用。

WebAssembly VS JavaScript 优点

更好性能

WebAssembly 和 JS 运行过程对比

  • 下载
    • JS 是高级语言,便于开发者阅读和编写,文件体积比汇编语言大。
    • WebAssembly 不是设计用于手动编写的,也不是供人类阅读的。代码被编译为 WebAssembly 之后,字节码会以二进制格式而不是文本格式表示,这可以减小文件大小,从而支持快速传输和下载。
  • 解析/解码
    • JS 需要先通过词法分析、语法分析生成 AST,然后再由 AST 生成字节码。
    • WebAssembly 是一种字节码,不需要进行解析,只需要解码并且检查确认代码没有错误就可以了。
  • 编译(优化、反优化):
    • JS 是弱语言类型,无法预知变量类型。JIT 监测热点代码,在代码执行若干次后,优化编译器才将其编译为机器码并优化。如果数据类型被动态修改,优化编译器会进行反优化,下次执行时会回退到解释器解释执行。
    • WebAssembly 代码是静态类型的,即可以预知变量持有的值的类型。WebAssembly 代码可以从一开始就编译为机器码,无须先监测,因此第一次运行代码就可以看到性能提升。WebAssembly 更接近机器码,在编译阶段不需要做太多的优化;WebAssembly 代码的类型是确定的,不存反优化的阶段。
    • WebAssembly 二进制文件的设计方式使得模块验证可以在一轮内完成,其结构也支持并行编译文件的不同部分。
    • 浏览器厂商引入了流编译技术,在浏览器下载和接收文件时,该技术可以将 WebAssembly 代码编译为机器码。流编译支持 WebAssembly 模块下载完毕,即进行初始化,这样会显著加速模块的启动过程。
  • 执行
    • JS 是一种编程语言,更注重开发效率和代码可读性,开发者编写的代码不一定能发挥 JIT (just-in-time) 的优化机制。
    • WebAssembly 被设计为编译目标,专注于提供更加理想的指令(执行效率更高的指令)给机器,执行效率更高。
  • 垃圾回收
    • JS 引擎会自动进行垃圾回收,垃圾回收可能会在一个不合适的时机启动并造成性能损耗。大多数浏览器已经能给垃圾回收安排一个合理的启动时间,但还是会增加性能开销。
    • WebAssembly 不支持垃圾回收,内存操作都是手动控制的(像 C、C++一样),性能由开发者控制。

便携且安全

WebAssembly 独立于平台、独立于硬件和独立于语言,它对设备或浏览器没有任何特殊要求,这增强了其便携性;其设计原则是与其他 Web 技术和谐共处,并保持向后兼容。代码在内存安全的沙盒环境中进行验证和执行,可以防止安全漏洞和数据损坏。此外,像其他 Web 代码一样,它遵循浏览器的同源策略和授权策略。

支持多线程

WebAssembly 不直接支持多线程。但是,WebAssembly 程序可以在宿主环境中执行,这个宿主环境可能支持多线程。因此,WebAssembly 程序间可以通过宿主环境来协调多线程(通过 Web Worker、共享线性内存 SharedArrayBufferWebAssembly atomics )。详情可参见文章: Using WebAssembly threads from C, C++ and Rust

集成已有库

如果应用程序使用 C/C++、Rust 或任何其他兼容语言,WebAssembly 可以轻松地将代码或桌面应用程序用于 Web。如今(2022 年 12 月)生态已经非常丰富,除了用于 Rust 的 wasm-pack 和用于 C/C++Emscripten 之外,还有 AssemblyScriptTinyGo 凡此种种,详情可参见: Awesome Wasm Compilers

WebAssembly VS JavaScript 缺陷

尽管 WebAssembly 具有很多优点,但目前仍存在些缺陷,譬如:

  • 旧浏览器不支持:它只能在支持 WebAssembly 的浏览器(或 runtime)中运行;倘若环境不支持,需要使用其他方式来实现(假如要考虑兼容);
  • 不能直接访问 DOM:WebAssembly 目前还不能直接访问浏览器的 DOM 元素;如果代码要访问浏览器 DOM,需要调用 JavaScript 方法来实现( 已有计划实现 );
  • 存在局限:比起 JavaScript,编写相关代码不够灵活,需要经过编译;需要手动分配内存,且缺少用于自动内存管理的垃圾收集 (GC)(尽管有计划添加 GC);

显而易见,没有技术会是银弹,只有把技术放在适用的场景下才能达到事半功倍的效果;在开发效率,与运行效率两者之间,无法同时做到最优;根据不同环境、需求,做平衡才是更优的选择。因此,除了计划解决的部分,WebAssembly 携带部分缺陷在所难免;毕竟它是对 JavaScript 的补充。

WebAssembly 应用场景

WASM 并不是为了优化您的网站,而是为了在执行以下任务时将浏览器(和服务器端运行时,如 Node.js、Deno)提升到一个新的水平:

  • 机器学习:TensorFlow.js;
  • 视频编辑:Clipchamp、Mastershot;
  • 实时合作编辑:Figma;
  • 游戏开发:Unity Web;
  • AR/VR 直播应用:(非常低的延迟);
  • 音乐编辑和流媒体: FFMPEG.WASM
  • 平台仿真:AutoCAD、Google Earth;
  • 加密解密:JSVMP(编译及二进制特性);
  • 开发工具:vim web、SQLlite web;
  • 图像识别及处理: Squoosh.app ,多线程客户端图像压缩;
  • 开发人员工具:(编辑器、编译器、调试器……);
  • 代替 JavaScript 框架:Yew、Blazor、Tokamak、Prism;
  • …….

更多使用场景可参见: WebAssembly Use Cases ;任何需要大量编码和大量性能调整的事物,都是 WASM 的完美用例。如需使用 WASM 的开源项目列表,您可以访问 Made with WebAssembly 这个社区。下面是些声名远扬的项目:

  • Tensorflow :是将 AI 和 ML 带给 JS 开发人员的主要库之一;在它 添加了 wasm 后端支持后 ,与普通 JS 版本相比,模型的性能平均提高了 10 倍。
  • Unity :作为主要的游戏开发引擎之一,它能够将您的项目导出为与 Web 兼容。从 2018 年开始,这个过程是通过 编译成 WebAssembly 来完成的。这是正确使用 WASM 的强大功能的完美示例。
  • Google Earth :该产品已经存在超过 15 年,曾经是一个桌面应用程序。 现在感谢 WASM,他们通过将旧代码编译为 WASM 将其 移植到网络中(就像 Autodesk 为其 CAD 应用程序所做的一样)。
  • Yew :有没有想过将 Rust 用于您的网络应用程序?现在你可以了,感谢 Yew 和 Web Assembly 给我们带来的力量。你不仅可以将 Rust 用于你的 UI,而且这个框架还提供了一个多线程环境来工作。Github 拥有 26kb Star(备注:目前还不是 1.0。准备好因破坏 API 更改而进行重大重构)。
  • Blazor :构建美观的 Web 应用,使用 .NET 和 C# 的强大功能构建全栈 Web 应用程序,而无需编写一行 JavaScript。生态可参见 awesome-blazor
  • Pyodide :是基于 WebAssembly 的浏览器和 Node.js 的 Python 发行版。

WebAssembly 主要的几个概念

为了理解 WebAssembly 是如何在 Web 运行的,需要了解几个关键概念:

  1. Instance :一个包含它在运行时用到的所有状态,包含 Memory、Table、以及一系列导入值的 Module,一个 Instance 类似一个 ES2015 的模块,它被加载到具有特定导入集的特定全局变量中
  2. Module :通过浏览器编译成为可执行机器码的 WebAssembly 二进制文件,Module 是无状态的,类似 Blob,能够在 Window 和 Worker 之间通过 postMessage 共享,一个 Module 声明了类似 ES2015 模块类似的 import 和 export。
  3. Memory :一个可调整大小的 ArrayBuffer,其中包含由 WebAssembly 的低层次内存访问指令读取和写入的线性字节数组。
  4. Table :一个可调整大小的类型化引用数组(如函数),然而处于安全和可移植性的原因,不能作为原始字节存储在内存中。

WebAssembly 的 JavaScript API 提供给开发者创建 Module、Memory、Table 和 Instance 的能力,给定一个 WebAssembly 的 Instance,JS 代码可以同步的调用它的 exports – 被作为普通的 JavaScript 函数导出。任意 JavaScript 函数可以被 WebAssembly 代码同步的调用,通过将 JavaScript 函数作为 imports 传给 WebAssembly Instance。

因为 JavaScript 能够完全控制 WebAssembly 代码的下载、编译和运行,所以 JavaScript 开发者可以认为 WebAssembly 只是 JavaScript 的一个新特性:可以高效的生成高性能的函数。

1
2
import {foo} from "./myModule.wasm";
foo();

在未来, WebAssembly 模块可以以 ES2015 的模块加载形式加载,如通过 import<script type="module">,意味着 JS 可以获取、编译、和导入一个 WebAssembly 模块,就像导入 ES2015 模块一样简单。详见: WebAssembly 的 ES 模块集成提案

如何在应用里使用 WebAssembly?

WebAssembly 给 Web 平台添加了两块内容:一种二进制格式代码,以及一系列可用于加载和执行二进制代码的 API。时值 2022 年,生态已颇具规模,详情可参见: Awesome Wasm ;主要流行的入口有:

  • 使用 EMScripten 来移植 C / C++ 应用;
  • 编写 Rust 应用,然后将 WebAssembly 作为它的输出;
  • 使用 AssemblyScript ,它是一门类似 TypeScript 的语言,能够编译成 WebAssembly 二进制;

对于 Web 开发者来说,可是使用类 TypeScript 的形式来尝试 WebAssembly 的编写,而不需要学习 C 或 Rust 的细节,那么 AssemblyScript 或将会是最好的选择。AssemblyScript 将 TypeScript 的变体编译为 WebAssembly,使得 Web 开发者可以使用 TypeScript 兼容的工具链,例如 Prettier 、VSCode Intellisense,你可以查看 AssemblyScript 文档 来了解如何使用。

补充说明,如今已有大量的工具库被编译为 WebAssembly,并做了封装;譬如 Photon (一个高性能的 Rust 图像处理库,它编译为 WebAssembly,允许在本地和 Web 上进行安全、快速的图像处理),其使用方式与其他 npm 包没有区别;在基于 Vue3 开发的作品: 玉桃文飨轩 中就有使用,如您感兴趣,可移步源码实现: markdown2png

参考资料及资源网站

资源网站

参考资料

您可能感兴趣的文章


静晴轩 ~ 晚晴幽草轩
个人微信公众号晚晴幽草轩;名字取自:“天意怜幽草,人间重晚晴”。
专注互联网开发(Web 大前端、快应用、小程序),以及分享优质网站、AI 应用、效率工具、心得感悟等内容。

文章目录
  1. 1. WebAssembly 历史起源
  2. 2. 全面认识 WebAssembly
    1. 2.1. WebAssembly 诞生背景
    2. 2.2. WebAssembly 是什么?
    3. 2.3. WebAssembly 试图取代 JavaScript 吗?
      1. 2.3.1. 理解 WebAssembly 文件格式
  3. 3. WebAssembly 优缺点
    1. 3.1. WebAssembly VS JavaScript 优点
      1. 3.1.1. 更好性能
      2. 3.1.2. 便携且安全
      3. 3.1.3. 支持多线程
      4. 3.1.4. 集成已有库
    2. 3.2. WebAssembly VS JavaScript 缺陷
  4. 4. WebAssembly 应用场景
  5. 5. WebAssembly 主要的几个概念
  6. 6. 如何在应用里使用 WebAssembly?
  7. 7. 参考资料及资源网站
    1. 7.1. 资源网站
    2. 7.2. 参考资料
  8. 8. 您可能感兴趣的文章