WebAssembly漫谈

1,715 阅读5分钟

WebAssembly是什么

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
—— webassembly.org/

从官网释义来看,Wasm是基于栈式虚拟机的二进制指令格式。作为高级编程语言的编译目标,它具有强可移植性,允许客户端、服务端应用部署在web上。

诞生背景

JavaScript缺陷

先来看看 JavaScript 代码在 V8 引擎中是怎么工作的[1]。

  • JavaScript 进入 Parser(解析器),Parser 会将其转化成 AST(抽象语法树)
  • 根据抽象语法树,Interpreter(解释器)会生成引擎能够直接阅读和执行的 Bytecode(字节码)
  • 接着,Compiler(编译器)将字节码逐行翻译成可高效执行的 Machine Code(机器码)
  • 对于执行次数较多的函数,引擎会将其编译成 Machine Code 并进行 JIT 优化(Just-In-Time编译) ,下次再执行这个函数的时候,就会直接执行编译好的 Machine Code。

不像 C++、JAVA 这些强类型语言,JavaScript 是一种弱类型的语言,需要在运行过程中动态编译。一个 JS变量,可能在上一秒是个Number,下一秒就变成了Array,这种灵活性使得代码在引擎中的优化有限。

asm.js

既然 JavaScript 存在因为弱类型带来的天生缺陷,那么,是否可以通过特殊写法使其具有隐式类型推断功能,来对代码性能进行优化呢?鉴于此,WebAssembly的前身,asm.js 出现了。

举个例子,下述代码中,|0 会使引擎推断出变量 a 是一个整数。

let a = 1.2 | 0
console.log(a) // 1

但是,asm.js 并不能解决所有问题:

  • asm.js 是 JavaScript 的严格子集,开发者无法使用 JS 的所有功能;
  • asm.js 同样是作为一种编译目标存在的(Emscripten 可将 C++/C 编译成 asm.js),虽然语法上比 WebAssembly 清楚得多,但是靠人工手写还是有一定难度;
  • 此外,无论 asm.js 对类型推导的问题做得再好,它始终逃不过要经过 Parser 和 Interpreter 这两步,而这两步是 JS 代码在引擎执行过程中最耗时的两步。

WebAssembly出现

鉴于 asm.js 存在的问题,WebAssembly 跳过了 Parser 和 Interpreter 这两步,作为编译后的字节码(同样通过 Emscripten 编译)直接在浏览器中运行,极大地提高了代码在浏览器中的运行速度。下面是一张示意图。

除了运行速度之外,Wasm 还具有以下优点:

  • 特有的二进制格式有效地减小了包体积,进一步提升了浏览器的加载速度;
  • 支持将 10+ 种编程语言(C/C++, Rust, Go等)编译成 Wasm,这意味着可以借鉴其他语言的生态,使其他语言下的标准类库在浏览器中直接使用;
  • 安全的沙箱运行环境,在浏览器中同样支持同源策略等权限限制;
  • 各大主流浏览器厂商(Firefox, Chrome, Safari, Edge等)相继支持 WebAssembly,并不断完善标准。目前,WebAssembly 已成为继 HTML、CSS、JavaScript 之后的第 4 种 Web 语言。

实操演示

先大致描述下 Wasm 程序的工作过程:

  1. 使用其他语言(C/C++, Rust, Go等)编写程序,并通过各自的工具链编译为 WebAssembly 文件(.wasm格式);
  2. 通过 fetch、XMLHttpRequest 等获取 .wasm 文件,得到一串 ArrayBuffer;
  3. 将 ArrayBuffer 编译为浏览器可执行的模块,并实例化;
  4. 调用从 Wasm 模块内导出的方法,完成所需操作。

下面来看一个实际的例子:

环境安装

  • Git:用于拉取 Emsdk 等
  • CMake:编译 C 语言
  • Host system compiler(宿主环境编译工具):Linux - GCC、OSX - Xcode、 Win - VS Code
  • Python 2.7+
  • Emscripten SDK:Wasm 编译器
git clone https://github.com/juj/emsdk.git
cd emsdk
./emsdk.bat install latest
./emsdk.bat activate latest
./emsdk_env.bat // 配置环境变量,每次重新登录或者新建shell窗口,都要执行一次这行命令

Emscripten编译C语言文件

int multiply(int a,int b){
    return a * b;
}

执行 emcc index.c -Os -s WASM=1 -s SIDE_MODULE=1 -o math.wasm,生成 math.wasm。 ​

加载调用

<html>
<head>
  <script>
    // Check for wasm support.
    if (!('WebAssembly' in window)) {
      alert('you need a browser with wasm support enabled :(');
    }

    function loadWebAssembly(filename, imports = {}) {
      return fetch(filename)
        .then(response => response.arrayBuffer())
        .then(buffer => {
          imports.env = imports.env || {}
          Object.assign(imports.env, {
            memoryBase: 0,
            tableBase: 0,
            __memory_base: 0,
            __table_base: 0,
            memory: new WebAssembly.Memory({ initial: 256, maximum: 256 }),
            table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc' })
          })
          return WebAssembly.instantiate(buffer, imports)
        })
        .then(result => result.instance)
    }

    loadWebAssembly('math.wasm')
      .then(instance => {
        const { multiply } = instance.exports
        var button = document.getElementById('run');
        button.value = 'Call wasm multiply';
        button.addEventListener('click', function() {
          alert('60 * 11 = ' + multiply(60, 11))
        }, false);
      })
  </script>
</head>
<body>
  <input type="button" id="run" value="waiting for WebAssembly..."/>
</body>
</html>

http-server 在本地启动一个静态资源服务器并访问页面,可看到在点击按钮后,弹窗显示 660,说明我们成功地在 JS 中调用了 C 文件的方法。

应用场景

目前,Wasm 主要有以下几类应用场景:

1 性能

对于那种计算密集型,或者对性能要求高的场景,如:图像/视频解码、图像处理、3D/WebVR/AR 等,Wasm 的优势明显。如:

  • Bilibili:当你的视频还在上传中,已经可以自由选择AI推荐的封面。这里采用了 webassembly+AI 的前端整合。Wasm 负责读取本地视频,生成图片;tensorflow.js 负责加载 AI 训练过的 model,读取图片并打分。
  • Figma:一个基于浏览器的协作式 UI 设计工具,作用类似于 Sketch。

2 复用

Wasm 可将多种高级语言编译为二进制码,这使在浏览器中复用其他语言的生态成为可能。如:

3 跨平台

WASI(WebAssembly System Interface)的出现,使得 WebAssembly 也能应用在非浏览器的环境中,这给了 Serverless 的更大规模落地有了更多的想象空间[4]。

发展现状

开发语言

目前,开发者们最喜爱的 Wasm 开发语言有:Rust, C++, AssemblyScript等。其中,AssemblyScript 是专为 Wasm 设计的类 TS 的语言,方便前端开发者上手。

应用类型

现阶段的 WebAssembly 应用以 Web 应用开发为主,但具体是哪些类型的 Web 应用还有待调研。此外,在游戏开发、Serverless应用、容器化、音视频处理、IoT等方面也有较多应用。 image.png

参考

最后

搜索公众号Eval Studio,关注我们,获取更多动态