内容简介:人们都说 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 文件的机器代码。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机网络(第6版)
[美] James F.Kurose、[美] Keith W.Ross / 陈鸣 / 机械工业出版社 / 2014-10 / 79.00元
《计算机网络:自顶向下方法(原书第6版)》第1版于12年前出版,首创采用自顶向下的方法讲解计算机网络的原理和协议,出版以来已被几百所大学和学院选用,是业界最经典的计算机网络教材之一。 《计算机网络:自顶向下方法(原书第6版)》第6版继续保持了以前版本的特色,为计算机网络教学提供了一种新颖和与时俱进的方法,同时也进行了相当多的修订和更新:第1章更多地关注时下,更新了接入网的论述;第2章用pyt......一起来看看 《计算机网络(第6版)》 这本书的介绍吧!