WebAssembly 的现在和未来

1,587 阅读6分钟

本文为翻译

原文标题:Where is WebAssembly now and what’s next?

原文作者:Lin Clark

原文地址:hacks.mozilla.org/2017/02/whe…

这是 WebAssembly 文章系列的第六部分。如果你还没有读过其他的,我建议你 从头开始

在 2017/2/28,四大主流浏览器宣布完成了 WebAssembly 的 MVP(Minimum viable product,最简可行产品) 。并提供了一个可发布的初始稳定版本。

浏览器该版本包含了驱动 WebAssembly 的 核心稳定版代码。核心代码并不包含 社区小组 规划的所有特性,但也足够让 WebAssembly 保持可用和快速了。

有了它,开发者就可以发布运行 WebAssembly 代码了。为了应对更老的浏览器,开发者可以降级发送一份 asm.js 版本的代码。由于 asm.js 是 JavaScript 的一个子集,所以任何 JS 引擎都可以运行它。利用 Emscripten,我们可以将同一份应用程序代码编译为 WebAssembly 和 asm.js。

即使在初始发行版中,WebAssembly 也是很快的。不过通过修复 Bug 和 增添新特性,WebAssembly 会在未来变得更快。

提升浏览器中的 WebAssembly 性能

随着浏览器在自身引擎中不断改进对 WebAssembly 的支持,WebAssembly 速度还会有所提升。目前浏览器厂商们正在尝试解决这些独立问题。

JS 与 WebAssembly 之间更快的函数调用

目前,在 JS 代码中调用 WebAssembly 函数会比预期更慢。这是因为它必须做一种被称为 “蹦床” 1 的工作。JIT 并不知道如何直接处理 WebAssembly,所以它必须将 WebAssembly 送到一些可以处理的地方。这段代码在引擎中执行很慢,它的功能是 设定运行优化的 WebAssembly 代码。

假如 JIT 能够直接处理 WebAssembly,那可能会比目前快 100 倍。

只将单一的大型任务交给 WebAssembly 处理的话,你不会注意到这类开销。但如果在 WebAssembly 和 JS 之间频繁的来回交替(比如处理一些小型的任务),那么这种开销就会变得明显。

更快的加载时间

JIT 必须在 加载时间 和 更快的执行时间 之间权衡。如果花更多的时间提前编译和优化,就能提升执行速度,但这会减慢初始启动速度。

很多持续进行的工作都是在 预编译(这个步骤旨在确保代码开始运行时,已经没有垃圾了)和 大部分代码运行次数没有频繁到值得去做优化 之间做平衡。

由于 WebAssembly 不需要推测使用的类型,因此引擎不用在运行时监视类型。这给了 WebAssembly 更多的选择,比如 并行编译和执行。

另外,最近新增的 JavaScript API 提供给了 WebAssembly 流式编译的能力。这意味着引擎可以在下载 WebAssembly 时,开始进行编译。

在 Firefox 中,我们正在开发一个 双编译系统。一个编译器会提前开始优化代码。在代码运行时,另一个编译器会在后台做全量的优化。全量优化的版本一旦就绪,就会切换过去。

将 后MVP 特性增添到规范中

WebAssembly 的其中一个目标是 制定小规模的规范 并持续进行测试,而非预先设计好了一切。

这意味着很多特性是在预期之中的,但还没有经过 100% 的思考。这些特性必须通过所有的浏览器厂商积极参与的规范流程。

这些特性被称为未来特性。下面我会列出其中一部分。

直接调用 DOM

目前,WebAssembly 还无法与 DOM 进行交互。这意味着无法以类似 element.innerHTML 的方式在 WebAssembly 中更新节点。

相反,你只能通过 JS 设置值。换句话说,就是需要向 JavaScript 调用者 回传一个值。另一方面,这意味着从 WebAssembly 调用一个 JavaScript 函数 — JavaScript 和 WebAssembly 函数都可以被 WebAssembly 模块导入。

无论哪种方式,经过 JavaScript 就一定会比直接访问更慢。在这个问题解决之前,WebAssembly 的一些应用可能会被搁置。

共享内存并发

并行执行代码中的不同部分是提升运行速度的一种方式。虽说有时这会适得其反,因为线程间的通信产生的开销可能会花费超过任务本身执行的时间。

但是,如果你能在线程之间共享内存,你就减少了这种开销。为此,WebAssembly 可以使用 JavaScript 的新 API SharedArrayBuffer。一旦这个 API 在浏览器中实现,工作组就可以开始制定 WebAssembly 与其 协作的方式。

SIMD

如果你读过或听过其他的一些有关 WebAssembly 文章或演讲,那你也许听说过 SIMD 支持。这个缩写代表了 Single Instruction, Multiple Data (单指令,多数据)。这是另一种处理并行事务的模式。

SIMD 让获取大型数据结构成为可能,比如对于 不同数字的向量,同一个指令可以同时处理不同的部分。通过这种方式,可以极大的加速 游戏 或 VR 中的各种复杂计算。

这对于大部分 web 应用程序开发者来说,并不是很重要。但这对于 多媒体开发者,比如 游戏开发者 来说,非常重要。

异常处理

很多代码库(比如使用 C++ 作为语言的)都会使用 异常(exception)。然而,在 WebAssembly 中 异常 还没有被规范化。

如果使用 Emscripten 编译代码,它会对应一些编译器优化等级 模拟异常处理。这么做还是挺慢的,所以你也可以选择用 DISABLE_EXCEPTION_CATCHING 标志关闭这个功能。

如果能够在 WebAssembly 原生处理异常,就没有必要进行模拟了。

其他改进 — 为开发者提供便利

某些未来特性不会影响性能,但是会让开发者使用 WebAssembly 更便利。

  • 优秀的源码级开发者工具 。目前,在浏览器中调试 WebAssembly 就像是在调试原始的汇编代码。没多少开发者能在脑中将 源码 和 汇编代码 对应起来。我们正在研究并改善工具支持,以便开发者能够调试自己的源码。
  • 垃圾回收 。只要提前定义了类型,就可以将代码转换成 WebAssembly。因此,使用 TypeScript 这样的代码应当是可以被编译为 WebAssembly 的。而目前唯一的一个问题是,WebAssembly 不知道如何与现有的垃圾回收器交互,比如 JS 引擎内部的那个。这个未来特性的主要想法是,为 WebAssembly 提供一套底层 GC 原始类型和操作来对 内置 GC 完美地进行访问。
  • ES6 模块集成 。浏览器正在添加 使用 script 标签来加载 JavaScript 模块的支持。新特性添加之后, 即使 <script src=url type="module"> 中 url 指向 WebAssembly 模块,也可以正常生效。

总结

如今的 WebAssembly 已经很快了,而随着浏览器不断 增添新特性 和 优化实现方式,它会变得更快。

Footnotes

  1. trampolining,参考比较常见的翻译就是蹦床,但个人认为可以译为 跳板 更合适