内容简介:人们都说 WebAssembly 是一个游戏规则改变者,因为它可以让代码更快地在网络上运行。有些加速已经存在,还有些在不远的将来。其中一种加速是流式编译,即浏览器在代码还在下载的时候就对其进行编译。截至目前,这只是潜在的未来加速(方式)。但随着下周 Firefox 58 版本的发布,它将成为现实。Firefox 58 还包含两层新的编译器。新的基线编译器编译代码的速度比优化编译器快了 10-15 倍。
人们都说 WebAssembly 是一个游戏规则改变者,因为它可以让代码更快地在网络上运行。有些加速已经存在,还有些在不远的将来。
其中一种加速是流式编译,即浏览器在代码还在下载的时候就对其进行编译。截至目前,这只是潜在的未来加速(方式)。但随着下周 Firefox 58 版本的发布,它将成为现实。
Firefox 58 还包含两层新的编译器。新的基线编译器编译代码的速度比优化编译器快了 10-15 倍。
综合起来,这两个变化意味着我们编译代码的速度比从网络中编译代码速度快。
在台式电脑上,我们每秒编译 30-60 兆字节的 WebAssembly 代码。这比网络传送数据包的速度还快。
如果你使用 Firefox Nightly 或者 Beta,你可以在你自己设备上试一试。即便是在很普通的移动设备上,我们可以每秒编译 8 兆字节 —— 这比任何移动网络的平均下载速度都要快得多。
这意味着你的代码几乎是在它完成下载后就立即执行。
为什么这很重要?
当网站发布大批量 JavaScript 代码时,Web 性能拥护者会变得束手无策。这是因为下载大量的 JavaScript 会让页面加载变慢。
这很大程度是因为解析和编译时间。正如Steve Souder 指出,网络性能的旧瓶颈曾是网络。但现在网络性能的新瓶颈是 CPU,特别是主线程。
所以我们想要尽可能多的把工作从主线程中移除。我们也想要尽可能早的启动它,以便我们充分利用 CPU 的所有时间。更好的是,我们可以完全减少 CPU 工作量。
使用 JavaScript 时,你可以做一些这样的事情。你可以通过流入的方式在主线程外解析文件。但你还是需要解析它们,这就需要很多工作,并且你必须等到它们都解析完了才能开始编译。然后编译的时候,你又回到了主线程上。这是因为 JS 通常是运行时延迟编译的。
使用 WebAssembly,启动的工作量减少了。解码 WebAssembly 比解析 JavaScript 更简单,更快捷。并且这些解码和编译可以跨多个线程进行拆分。
这意味着多个线程将运行基线编译,这会让它变得更快。一旦完成,基线编译好的代码就可以在主线程上开始执行。它不必像 JS 代码一样暂停编译。
当基线编译的代码在主线程上运行时,其他线程则在做更优化的版本。当更优化的版本完成时,它就会替换进来使得代码运行更加快捷。
这使得加载 WebAssembly 的成本变得更像解码图片而不是加载 JavaScript。并且想想看 —— 网络性能倡导者肯定接受不了 150kB 的 JS 代码负载量,但相同大小的图像负载量并不会引起人们的注意。
这是因为图像的加载时间要快得多,就像 Addy Osmani 在JavaScript 的成本 中解释的那样,解码图像并不会阻塞主线程,正如 Alex Russell 在 你能接受吗?真实的 Web 性能预算 中所讨论的那样。
但这并不意味着我们希望 WebAssembly 文件和图像文件一样大。虽然早期的 WebAssembly 工具创建了大型的文件,是因为它们包含了很多运行时(内容),目前来看还有很多工作要做让文件变得更小。例如,Emscripten 有一个 “缩小协议” 。在 Rust 中,你已经可以通过使用 wasm32-unknown-unknown 目标来获取相当小尺寸的文件,并且还有像 wasm-gc 和 wasm-snip 这样的 工具 来帮助进一步优化它们。
这就意味着这些 WebAssembly 文件的加载速度要比等量的 JavaScript 快得多。
这很关键。正如Yehuda Katz 指出,这是一个游戏规则改变者。
所以让我们看看新编译器是怎么工作的吧。
流式编译:更早开始的编译
如果你更早开始编译代码,你就更早完成它。这就是流式编译所做的 —— 尽可能快地开始编译 .wasm 文件。
当你下载文件时,它不是单件式的。实际上,它带来的是一系列数据包。
之前,当 .wasm 文件中的每个包正在下载时,浏览器网络层会把它放进 ArrayBuffer(译者注:数组缓存)中。
然后,一旦完成下载,它会将 ArrayBuffer 转移到 Web VM(也就是 JS 引擎)中。也就到了 WebAssembly 编译器要开始编译的时候。
但是没有充分的理由让编译器等待。从技术上讲,逐行编译 WebAssembly 是可行的。这意味着你能够在第一个块进来的时候就开始启动。
所以这就是我们新编译器所做的。它利用了 WebAssembly 的流式 API。
如果你提供给 WebAssembly.instantiateStreaming
一个响应的对象,则(对象)块一旦到达就会立即进入 WebAssembly 引擎。然后编译器可以开始处理第一个块,即便下一个块还在下载中。
除了能够并行下载和编译代码外,它还有另外一个优势。
.wasm 模块中的代码部分位于任何数据(它将引入到模块的内存对象)之前。因此,通过流式传输,编译器可以在模块的数据仍在下载的时候就对其进行编译。如果当你的模块需要大量的数据,且可能是兆字节的时候,这些就会显得很重要。
通过流式传输,我们可以提前开始编译。而且我们同样可以更快速地进行编译。
第 1 层基线编译器:更快的编译代码
如果你想要代码跑的快,你就需要优化它。但是当你编译时执行这些优化会花费时间,也就会让编译代码变得更慢。所以这里需要一个权衡。
但鱼和熊掌可以兼得。如果我们使用两个编译器,就能让其中一个快速编译但是不做过多的优化工作,而另一个虽然编译慢,但是创建了更多优化的代码。
这就称作为层编译器。当代码第一次进入时,将由第 1 层(或基线)编译器对其编译。然后,当基线编译完成,代码开始运行之后,第 2 层编译器再一次遍历代码并在后台编译更优化的版本。
一旦它(译者注:第 2 层编译)完成,它会将优化后的代码热插拔为先前的基线版本。这使代码执行得更快。
JavaScript 引擎已经使用分层编译器很长一段时间了。然而,JS 引擎只在一些代码变得“温热” —— 当代码的那部分被调用太多次时,才会使用第 2 层(或优化)编译器。
相比之下,WebAssembly 的第 2 层编译器会热切地进行全面的重新编译,优化模块中的所有代码。在未来,我们可能会为开发者添加更多选项,用来控制如何进行激进的优化或者惰性的优化。
基线编译器在启动时节省了大量时间。它编译代码的速度比优化编译器的快 10-15 倍。并且在我们的测试中,它创建代码的速度只慢了 2 倍。
这意味着,只要仍在运行基线编译代码,即便是在最开始的几分钟你的代码也会运行地很快。
并行化:让一切更快
在 关于 Firefox Quantum 的文章 中,我解释了粗粒度和细粒度的并行化。我们可以用它们来编译 WebAssembly。
我在上文有提到,优化编译器会在后台进行编译。这意味着它空出的主线程可用于执行代码。基线编译版本的代码可以在优化编译器进行重新编译时运行。
但在大多数电脑上仍然会有多个核心没有使用。为了充分使用所有核心,两个编译器都使用细粒度并行化来拆解工作。
并行化的单位是功能,每个功能都可以在不同的核心上单独编译。这就是所谓的细粒度,实际上,我们需要将这些功能分批处理成更大的功能组。这些批次会被派送到不同的核心里。
...然后通过隐式缓存完全跳过所有工作(未来的任务)
目前,每次重新加载页面时都会重做解码和编译。但是如果你有相同的 .wasm 文件,它编译后都是一样的机器代码。
这意味着,很多时候这些工作都可以跳过。这些也是未来我们要做的。我们将在第一页加载时进行解码和编译,然后将生成的机器码缓存在 HTTP 缓存中。之后当你再次请求这个 URL 的时候,它会拉取预编译的机器代码。
这就能让后续加载页面的加载时间消失了。
这项功能已经有了基础构建。我们在 Firefox 58 版本中 缓存了这样的 JavaScript 字节代码 。我们只需扩展这种支持来缓存 .wasm 文件的机器代码。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
自媒体写作,从基本功到实战方法
余老诗 / 清华大学出版社 / 2018-9-1 / 59.00元
《自媒体写作》是一本系统而通俗易懂的自媒体写作指导书。 全书共分为10章,分别从写作基本功、新媒体认知、新媒体传播规律和自媒体作者阅读写作素养以及如何进阶等方面展开,结合简书、公众号、今日头条等主流自媒体所选例文,讲解写作知识和新媒体特点,内容详实,有理有据,非常适合自媒体写作爱好者自学。 尤其值得一提的是,写作基本功部分从原理、方法和技巧三个层面展开论说,让自媒体写作学习者既能从根本......一起来看看 《自媒体写作,从基本功到实战方法》 这本书的介绍吧!