图说 WebAssembly(六):现状与展望

81357216 2019-06-27

本文是图说 WebAssembly 系列文章的最后一篇。如果您还未阅读之前的文章,建议您从第一篇入手。

现状

2017 年 2 月 28 日,主流的四大浏览器达成了共识并宣布 WebAssembly 的最小可行产品(MVP)已经完成。这是 WebAssembly 搭载在浏览器上的第一个稳定初始版本。

图说 WebAssembly(六):现状与展望

这也为浏览器提供了一个稳定的 WebAssembly 核心。虽然该核心还不好汉社区组正在计划的所有功能,但它也确实提供了足够的功能,使得 WebAssembly 快速且可用。

从这一刻开始,开发者也可以开始发布 WebAssembly 代码了。对于较早版本的浏览器,开发者可以回退到使用 asm.js 版本的代码。因为 asm.js 是 JavaScript 的子集,所以任何 JavaScript 引擎都可以运行它。
如果使用 Emscripten 的话,还可以把相同的应用编译为 WebAssembly 版本和 asm.js 版本的代码。

即使是最初的发行版本,WebAssembly 也是高性能的。但是随着问题的修复和新功能的添加,它在未来将会拥有更高性能。

提高性能

随着浏览器逐步改进对 WebAssembly 的支持,性能提升也会慢慢的显现。目前,浏览器厂商们正在独自改进下面这些问题。

更快的函数调用

当前,在 JavaScript 中调用 WebAssembly 函数比想象的要慢。这是因为这个过程必须经历一个称为弹跳(Trampolining)的过程。JIT 并不知道如何直接处理 WebAssembly,所以它必须把 WebAssembly 转移到知道如何处理它的地方去。这在引擎内部是一个很慢的过程,该过程会建立用来运行 WebAssembly 代码的准备过程。

图说 WebAssembly(六):现状与展望

这个种处理方式可能比直接由 JIT 处理慢 100 倍。

如果将一个大型任务交给 WebAssembly 模块来完成的话,我们可能不会注意到这种开销。
但是如果我们在 WebAssembly 和 JavaScript 之间来回多次调用,那么这个问题就凸显出来了。

更快的加载

JIT 必须在更快的加载时间和更快的运行之间做出权衡。
如果花费更多的时间在编译和优化,虽然可以提升运行速度,但是也降低了启动性能。

一个基本事实是,大多数的代码的运行次数其实都还达不到需要优化的地步。
不过,目前有许多正在进行的研究,在寻找预编译和这样的基本事实之间的平衡点。

由于 WebAssembly 并不需要推测数据类型,所以引擎也不需要在运行时监视这些数据类型的变化。
这也就给了我们更多的选择余地,例如把编译和运行并行化。

此外,最近新增的 JavaScript API 允许对 WebAssembly 进行流式编译。也就是说,引擎可以在 .wasm 还没下载完成的时候就开始对已下载的内容进行编译。

在 Firefox,我们正在开发双编译系统。一个编译器会提前运行,并把代码优化工作做得相当不错。等到运行代码的时候,另一个编译器会在后台进行全优化工作。一旦代码优化完成,便会立即替换掉旧代码。

新增功能

WebAssembly 的设计目标之一是先支持小部分功能并同步进行测试,而不是从一开始就把方方面面都设计好。

因此,它还有更多功能值得期待,不过新功能还没进行周全的考虑。只有所有浏览器厂商都积极参与才能把新功能写进规范。

以下是一些新功能。

直接操作 DOM

目前,WebAssembly 没有任何方式能够操作 DOM 。所以我们不能像使用 element.innerHTML 一样,从 WebAssembly 里更新一个 DOM 节点。

相反,必须通过 JavaScript 才能改变 DOM 节点。
也就是说,必须把新值返回给 JavaScript 调用方。或者在 WebAssembly 中调用 JavaScript 函数。这是可以实现的,因为 JavaScript 和 WebAssembly 函数都可以做为 WebAssembly 模块的导入。

图说 WebAssembly(六):现状与展望

不管使用哪种方式,绕道 JavaScript 肯定比直接操作 DOM 要慢。这就导致了部分 WebAssembly 应用可能会因此而推迟发布时间。

共享内存并发

有一种提高运行速度的办法是,使不同代码同时并行运行。
不过这有时候可能会偷鸡不成蚀把米,因为线程间通信可能比任务本身耗费的时间还多。

但是如果能够在不同线程之间共享内存,那么这种情况就会好很多。为此,WebAssembly 可以使用 JavaScript 的新接口 SharedArrayBuffer 来实现内存共享。一旦浏览器开始支持 SharedArrayBuffer,WebAssembly 工作小组就立马可以开始为之制定相关标准。

SIMD

SIMD(Single Instruction, Multiple Data)是“单指令多数据”的缩写。它是实现并行化的另一种方式。

SIMD 可以接受一个大型数据结构(比如一个包含不同数值的向量),然后对其中的不同数据同时应用相同的指令。这种方式可以大幅提升游戏或者 VR 中的各种复杂计算。

这对普通的网页应用开发者可能没那么重要。但是对于像游戏等多媒体应用开发者就显得尤为重要了。

异常处理

很多语言都支持异常处理,但是 WebAssembly 尚未有相关异常处理的规范。

如果你使用 Emscripten 编译代码,你会发现它会模拟某些编译器优化级别的异常处理。
不过这会导致编译变慢,但是你可以通过 DISABLE_EXCEPTION_CATCHING 标志来关闭它。

如果 WebAssembly 本身就能够处理异常,那么这种异常模拟就没必要了。

提升开发体验的改进

也有一些未来的功能并不会影响性能,但是却能提升 WebAssembly 的开发体验。

  • 一等的源码开发工具。目前,在浏览器中调试 WebAssembly 就像直接调试汇编。虽然还是能够调试,但是基本上很少开发者能够把它跟源码关联起来。所以,我们正在研究该如何改进工具支持,从而实现可以直接调试源码。
  • 垃圾回收。如果能够事先定义数据的类型,那么就能够把这类代码变成 WebAssembly 。所以使用 TypeScript 这类语言编写的代码,是可以跟 WebAssembly 兼容的。不过,目前唯一的困难是 WebAssembly 还不知道该如何与现有的垃圾回收机制结合,就像 JavaScript 引擎内置的垃圾回收一样。
  • ES6 模块集成。浏览器目前正在完善使用 script 来加载模块的功能。等到该功能完成后,把 <script src=url type="module"> 这种标签指向 WebAssembly 模块也应该是能够支持的。

结束

现如今 WebAssembly 已经相当快速。随着新功能和改进逐步实现,相信它一定可以变得更快!

相关推荐