3

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

现状

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

logo_party01-500x169.png

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

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

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

提高性能

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

更快的函数调用

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

06-02-trampoline01.png

这个种处理方式可能比直接由 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 模块的导入。

06-03-dom01-768x642.png

不管使用哪种方式,绕道 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 已经相当快速。随着新功能和改进逐步实现,相信它一定可以变得更快!


mingzhong
2.1k 声望3.2k 粉丝

世界很美好,时间很宝贵。