[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

栏目: 后端 · 前端 · 发布时间: 6年前

内容简介:人们都说 WebAssembly 是一个游戏规则改变者,因为它可以让代码更快地在网络上运行。有些加速已经存在,还有些在不远的将来。其中一种加速是流式编译,即浏览器在代码还在下载的时候就对其进行编译。截至目前,这只是潜在的未来加速(方式)。但随着下周 Firefox 58 版本的发布,它将成为现实。Firefox 58 还包含两层新的编译器。新的基线编译器编译代码的速度比优化编译器快了 10-15 倍。

人们都说 WebAssembly 是一个游戏规则改变者,因为它可以让代码更快地在网络上运行。有些加速已经存在,还有些在不远的将来。

其中一种加速是流式编译,即浏览器在代码还在下载的时候就对其进行编译。截至目前,这只是潜在的未来加速(方式)。但随着下周 Firefox 58 版本的发布,它将成为现实。

Firefox 58 还包含两层新的编译器。新的基线编译器编译代码的速度比优化编译器快了 10-15 倍。

综合起来,这两个变化意味着我们编译代码的速度比从网络中编译代码速度快。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

在台式电脑上,我们每秒编译 30-60 兆字节的 WebAssembly 代码。这比网络传送数据包的速度还快。

如果你使用 Firefox Nightly 或者 Beta,你可以在你自己设备上试一试。即便是在很普通的移动设备上,我们可以每秒编译 8 兆字节 —— 这比任何移动网络的平均下载速度都要快得多。

这意味着你的代码几乎是在它完成下载后就立即执行。

为什么这很重要?

当网站发布大批量 JavaScript 代码时,Web 性能拥护者会变得束手无策。这是因为下载大量的 JavaScript 会让页面加载变慢。

这很大程度是因为解析和编译时间。正如Steve Souder 指出,网络性能的旧瓶颈曾是网络。但现在网络性能的新瓶颈是 CPU,特别是主线程。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

所以我们想要尽可能多的把工作从主线程中移除。我们也想要尽可能早的启动它,以便我们充分利用 CPU 的所有时间。更好的是,我们可以完全减少 CPU 工作量。

使用 JavaScript 时,你可以做一些这样的事情。你可以通过流入的方式在主线程外解析文件。但你还是需要解析它们,这就需要很多工作,并且你必须等到它们都解析完了才能开始编译。然后编译的时候,你又回到了主线程上。这是因为 JS 通常是运行时延迟编译的。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

使用 WebAssembly,启动的工作量减少了。解码 WebAssembly 比解析 JavaScript 更简单,更快捷。并且这些解码和编译可以跨多个线程进行拆分。

这意味着多个线程将运行基线编译,这会让它变得更快。一旦完成,基线编译好的代码就可以在主线程上开始执行。它不必像 JS 代码一样暂停编译。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

当基线编译的代码在主线程上运行时,其他线程则在做更优化的版本。当更优化的版本完成时,它就会替换进来使得代码运行更加快捷。

这使得加载 WebAssembly 的成本变得更像解码图片而不是加载 JavaScript。并且想想看 —— 网络性能倡导者肯定接受不了 150kB 的 JS 代码负载量,但相同大小的图像负载量并不会引起人们的注意。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

这是因为图像的加载时间要快得多,就像 Addy Osmani 在JavaScript 的成本 中解释的那样,解码图像并不会阻塞主线程,正如 Alex Russell 在 你能接受吗?真实的 Web 性能预算 中所讨论的那样。

但这并不意味着我们希望 WebAssembly 文件和图像文件一样大。虽然早期的 WebAssembly 工具创建了大型的文件,是因为它们包含了很多运行时(内容),目前来看还有很多工作要做让文件变得更小。例如,Emscripten 有一个 “缩小协议” 。在 Rust 中,你已经可以通过使用 wasm32-unknown-unknown 目标来获取相当小尺寸的文件,并且还有像 wasm-gcwasm-snip 这样的 工具 来帮助进一步优化它们。

这就意味着这些 WebAssembly 文件的加载速度要比等量的 JavaScript 快得多。

这很关键。正如Yehuda Katz 指出,这是一个游戏规则改变者。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

所以让我们看看新编译器是怎么工作的吧。

流式编译:更早开始的编译

如果你更早开始编译代码,你就更早完成它。这就是流式编译所做的 —— 尽可能快地开始编译 .wasm 文件。

当你下载文件时,它不是单件式的。实际上,它带来的是一系列数据包。

之前,当 .wasm 文件中的每个包正在下载时,浏览器网络层会把它放进 ArrayBuffer(译者注:数组缓存)中。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

然后,一旦完成下载,它会将 ArrayBuffer 转移到 Web VM(也就是 JS 引擎)中。也就到了 WebAssembly 编译器要开始编译的时候。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

但是没有充分的理由让编译器等待。从技术上讲,逐行编译 WebAssembly 是可行的。这意味着你能够在第一个块进来的时候就开始启动。

所以这就是我们新编译器所做的。它利用了 WebAssembly 的流式 API。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

如果你提供给 WebAssembly.instantiateStreaming 一个响应的对象,则(对象)块一旦到达就会立即进入 WebAssembly 引擎。然后编译器可以开始处理第一个块,即便下一个块还在下载中。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

除了能够并行下载和编译代码外,它还有另外一个优势。

.wasm 模块中的代码部分位于任何数据(它将引入到模块的内存对象)之前。因此,通过流式传输,编译器可以在模块的数据仍在下载的时候就对其进行编译。如果当你的模块需要大量的数据,且可能是兆字节的时候,这些就会显得很重要。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

通过流式传输,我们可以提前开始编译。而且我们同样可以更快速地进行编译。

第 1 层基线编译器:更快的编译代码

如果你想要代码跑的快,你就需要优化它。但是当你编译时执行这些优化会花费时间,也就会让编译代码变得更慢。所以这里需要一个权衡。

但鱼和熊掌可以兼得。如果我们使用两个编译器,就能让其中一个快速编译但是不做过多的优化工作,而另一个虽然编译慢,但是创建了更多优化的代码。

这就称作为层编译器。当代码第一次进入时,将由第 1 层(或基线)编译器对其编译。然后,当基线编译完成,代码开始运行之后,第 2 层编译器再一次遍历代码并在后台编译更优化的版本。

一旦它(译者注:第 2 层编译)完成,它会将优化后的代码热插拔为先前的基线版本。这使代码执行得更快。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

JavaScript 引擎已经使用分层编译器很长一段时间了。然而,JS 引擎只在一些代码变得“温热” —— 当代码的那部分被调用太多次时,才会使用第 2 层(或优化)编译器。

相比之下,WebAssembly 的第 2 层编译器会热切地进行全面的重新编译,优化模块中的所有代码。在未来,我们可能会为开发者添加更多选项,用来控制如何进行激进的优化或者惰性的优化。

基线编译器在启动时节省了大量时间。它编译代码的速度比优化编译器的快 10-15 倍。并且在我们的测试中,它创建代码的速度只慢了 2 倍。

这意味着,只要仍在运行基线编译代码,即便是在最开始的几分钟你的代码也会运行地很快。

并行化:让一切更快

关于 Firefox Quantum 的文章 中,我解释了粗粒度和细粒度的并行化。我们可以用它们来编译 WebAssembly。

我在上文有提到,优化编译器会在后台进行编译。这意味着它空出的主线程可用于执行代码。基线编译版本的代码可以在优化编译器进行重新编译时运行。

但在大多数电脑上仍然会有多个核心没有使用。为了充分使用所有核心,两个编译器都使用细粒度并行化来拆解工作。

并行化的单位是功能,每个功能都可以在不同的核心上单独编译。这就是所谓的细粒度,实际上,我们需要将这些功能分批处理成更大的功能组。这些批次会被派送到不同的核心里。

...然后通过隐式缓存完全跳过所有工作(未来的任务)

目前,每次重新加载页面时都会重做解码和编译。但是如果你有相同的 .wasm 文件,它编译后都是一样的机器代码。

这意味着,很多时候这些工作都可以跳过。这些也是未来我们要做的。我们将在第一页加载时进行解码和编译,然后将生成的机器码缓存在 HTTP 缓存中。之后当你再次请求这个 URL 的时候,它会拉取预编译的机器代码。

这就能让后续加载页面的加载时间消失了。

[译] 使 WebAssembly 更快:Firefox 的新流式和分层编译器

这项功能已经有了基础构建。我们在 Firefox 58 版本中 缓存了这样的 JavaScript 字节代码 。我们只需扩展这种支持来缓存 .wasm 文件的机器代码。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

单元测试之道Java版

单元测试之道Java版

David Thomas、Andrew Hunt / 陈伟柱、陶文 / 电子工业 / 2005-1 / 25.00元

程序员修炼三部曲丛书包含了四本书,介绍了每个注重实效的程序员和成功团队所必备的一些工具。 注重实效的程序员都会利用反馈来指导开发,并驱动个人的开发流程。编码的时候,最有用的反馈来自于“单元测试”。 为了测试一座桥梁,不会只在晴朗的天气,开一辆汽车从桥中间穿过,就认为已经完成了对桥梁的测试。然而许多程序员却正在使用这种测试方法——把这种一次顺利通过称为“测试”。事实上,注重实效的程序员应......一起来看看 《单元测试之道Java版》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码