WebAssembly 为什么这么快?

这是关于 WebAssembly 以及它为什么这么快的一系列文章的第五篇。如果您还没有阅读其他文章,我们建议您从头开始阅读

上一篇文章中,我解释了用WebAssembly 还是 JavaScript 编程并不是非此即彼的选择。我们不希望太多的开发者编写完整的 WebAssembly 代码库。

因此,开发者不需要在 WebAssembly 和 JavaScript 之间为他们的应用程序选择。但是,我们确实希望开发者能够将部分 JavaScript 代码替换为 WebAssembly。

例如,React 团队可以将他们的协调器代码(又名虚拟 DOM)替换为 WebAssembly 版本。使用 React 的人无需做任何事情……他们的应用程序将像以前一样工作,只是他们将获得 WebAssembly 的好处。

React 团队这样的开发者进行这种替换的原因是 WebAssembly 更快。但是是什么让它更快呢?

如今 JavaScript 的性能如何?

在我们理解 JavaScript 和 WebAssembly 之间的性能差异之前,我们需要理解 JS 引擎所做的工作。

这张图粗略地描述了如今应用程序启动性能可能的样子。

JS 引擎在任何一项任务上花费的时间取决于页面使用的 JavaScript。这张图并不意味着代表精确的性能数据。相反,它旨在提供一个高层次的模型,说明相同功能在 JS 和 WebAssembly 中的性能差异。

Diagram showing 5 categories of work in current JS engines

每个条形图显示了花费在特定任务上的时间。

  • 解析 - 处理源代码以将其转换为解释器可以运行的内容所需的时间。
  • 编译 + 优化 - 在基线编译器和优化编译器中花费的时间。一些优化编译器的操作不在主线程上,因此这里没有包含。
  • 重新优化 - JIT 在其假设失败时花费的时间,包括重新优化代码和从优化代码退回到基线代码。
  • 执行 - 运行代码所需的时间。
  • 垃圾回收 - 花费在清理内存上的时间。

需要注意的一点是:这些任务不会以离散的块或特定的顺序发生。相反,它们会交织在一起。解析会进行一点,然后执行一些操作,然后编译一些操作,然后解析更多操作,然后执行更多操作,等等。

这种细分带来的性能是 JavaScript 早期的巨大进步,当时看起来更像是这样

Diagram showing 3 categories of work in past JS engines (parse, execute, and garbage collection) with times being much longer than previous diagram

最初,当它只是一个解释器运行 JavaScript 时,执行速度相当慢。当引入 JIT 时,它极大地提高了执行时间。

权衡是监控和编译代码的开销。如果 JavaScript 开发人员以当时的方式继续编写 JavaScript,那么解析和编译时间将非常小。但性能的提升促使开发者创建了更大的 JavaScript 应用程序。

这意味着仍然有改进的空间。

WebAssembly 如何比较?

以下是对 WebAssembly 与典型 Web 应用程序比较的近似值。

Diagram showing 3 categories of work in WebAssembly (decode, compile + optimize, and execute) with times being much shorter than either of the previous diagrams

浏览器在处理所有这些阶段的方式上略有差异。我在这里使用 SpiderMonkey 作为我的模型。

获取

图中没有显示这一点,但占用时间的因素之一是简单地从服务器获取文件。

由于 WebAssembly 比 JavaScript 更紧凑,因此获取速度更快。即使压缩算法可以显著缩小 JavaScript 包的大小,WebAssembly 的压缩二进制表示仍然更小。

这意味着在服务器和客户端之间传输它需要更少的时间。这在慢速网络上尤其如此。

解析

到达浏览器后,JavaScript 源代码将被解析为抽象语法树。

浏览器通常会延迟执行此操作,只在开始时解析真正需要的部分,并为尚未调用的函数创建存根。

从那里,AST 将转换为特定于该 JS 引擎的中间表示形式(称为字节码)。

相反,WebAssembly 不需要经历这种转换,因为它已经是中间表示形式。它只需要被解码和验证以确保其中没有错误。

Diagram comparing parsing in current JS engine with decoding in WebAssembly, which is shorter

编译 + 优化

正如我在关于JIT 的文章中解释的那样,JavaScript 在代码执行期间被编译。根据运行时使用的类型,可能需要编译同一代码的多个版本。

不同的浏览器以不同的方式处理 WebAssembly 的编译。一些浏览器在开始执行 WebAssembly 之前会对其进行基线编译,而另一些浏览器使用 JIT。

无论哪种方式,WebAssembly 都比机器码更接近。例如,类型是程序的一部分。这更快的原因有几个

  1. 编译器不需要花费时间运行代码来观察正在使用的类型,然后才开始编译优化代码。
  2. 编译器不需要根据观察到的不同类型编译同一代码的不同版本。
  3. LLVM 已经提前完成了更多的优化。因此,编译和优化它所需的工作更少。

Diagram comparing compiling + optimizing, with WebAssembly being shorter

重新优化

有时 JIT 必须放弃优化版本的代码并重新尝试。

当 JIT 根据运行代码收集的数据做出的假设被证明是错误的时候,就会发生这种情况。例如,当进入循环的变量与先前迭代中的变量不同,或者当在原型链中插入新函数时,就会发生反优化。

反优化有两个成本。首先,从优化代码退回到基线版本需要一些时间。其次,如果该函数仍然被频繁调用,JIT 可能会决定再次将其发送到优化编译器,因此第二次编译它也会有成本。

在 WebAssembly 中,类型是显式的,因此 JIT 不需要根据运行时收集的数据对类型做出假设。这意味着它不需要经历重新优化循环。

Diagram showing that reoptimization happens in JS, but is not required for WebAssembly

执行

有可能编写执行效率高的 JavaScript。要做到这一点,您需要了解 JIT 所做的优化。例如,您需要知道如何编写代码,以便编译器可以对其进行类型特化,如关于JIT 的文章中所述。

然而,大多数开发者并不了解 JIT 的内部机制。即使对于那些了解 JIT 内部机制的开发者来说,也很难达到最佳点。人们用来使代码更易读的许多编码模式(例如将常见任务抽象到跨类型工作的函数中)在编译器尝试优化代码时会妨碍它。

此外,JIT 使用的优化在不同的浏览器之间是不同的,因此针对一个浏览器的内部机制进行编码可能会使您的代码在另一个浏览器中性能下降。

因此,在 WebAssembly 中执行代码通常更快。JIT 对 JavaScript 进行的许多优化(例如类型特化)在 WebAssembly 中是不必要的。

此外,WebAssembly 被设计为编译目标。这意味着它被设计用于编译器生成,而不是用于人类程序员编写。

由于人类程序员不需要直接编程它,因此 WebAssembly 可以提供一组更适合机器的指令。根据您的代码正在执行的工作类型,这些指令的运行速度可以快 10% 到 800% 之间。

Diagram comparing execution, with WebAssembly being shorter

垃圾回收

在 JavaScript 中,开发者不需要担心在不再需要旧变量时将其从内存中清除。相反,JS 引擎会使用一种叫做垃圾回收器的东西自动完成这个工作。

但是,如果您想要可预测的性能,这可能是一个问题。您无法控制垃圾回收器何时执行其工作,因此它可能在不方便的时候出现。大多数浏览器在安排垃圾回收方面做得很好,但它仍然是可能阻碍代码执行的开销。

至少现在,WebAssembly 根本不支持垃圾回收。内存是手动管理的(就像在 C 和 C++ 等语言中一样)。虽然这可能会使开发者的编程变得更加困难,但也确实使性能更加一致。

Diagram showing that garbage collection happens in JS, but is not required for WebAssembly

结论

WebAssembly 在许多情况下比 JavaScript 更快,因为

  • 获取 WebAssembly 的时间更短,因为即使在压缩后,它也比 JavaScript 更紧凑。
  • 解码 WebAssembly 比解析 JavaScript 更快。
  • 编译和优化所需时间更短,因为 WebAssembly 比 JavaScript 更接近机器代码,并且已经在服务器端经过优化。
  • 不需要重新优化,因为 WebAssembly 内置了类型和其他信息,因此 JS 引擎在优化时不需要像处理 JavaScript 那样进行推测。
  • 执行通常所需时间更短,因为开发人员需要了解的编译器技巧和陷阱更少,才能编写始终如一的高性能代码,此外,WebAssembly 的指令集更适合机器。
  • 由于内存是手动管理的,因此不需要垃圾收集。

这就是为什么在许多情况下,WebAssembly 在执行相同任务时会比 JavaScript 性能更高。

在某些情况下,WebAssembly 的性能并不如预期,也有一些即将到来的变化将使其更快。我将在下一篇文章中介绍这些内容。

关于 Lin Clark

Lin 在 Mozilla 的高级开发部门工作,专注于 Rust 和 WebAssembly。

更多 Lin Clark 的文章...


20 条评论

  1. Art Scott

    而且 WA 更节能

    2017 年 2 月 28 日 下午 10:17

  2. Daniel Earwicker

    以 React 协调为例,该任务以 JS 对象树作为输入,并操作 DOM 作为其“输出”。

    由于 Web Assembly 现在还无法与这两者一起工作,它真的能加速这种情况吗?

    2017 年 2 月 28 日 下午 11:49

    1. Lin Clark

      首先,直接访问 DOM 正在路线图中,因此一旦实现,这就不应该成为限制。但新的 Fiber 协调器以效果列表作为输出,而不是直接操作 DOM,因此您可以从基于 WASM 的 Fiber 协调器中返回效果列表。在实际开始使用 WASM 实现进行试验之前,很难说边界在哪里。

      2017 年 2 月 28 日 下午 2:32

      1. Mike

        为什么要费心使用 DOM?为什么我们还需要 DOM / HTML / CSS 等等?Webassembly 不能被设计为直接控制浏览器渲染,从而跳过整个 HTML / DOM / CSS 混乱吗?现在不是时候向前迈进,使用类似于 Microsoft WPF 的框架来创建用户界面了吗?

        如果我在这里遗漏了重点,请原谅我,我对 web 编程知之甚少,所以我的评论可能完全没有道理。

        2017 年 3 月 7 日 上午 9:47

        1. Lin Clark

          有些人已经谈论过这样做。不过,这里有一些问题。例如,有一个从 DOM 生成的辅助功能树。辅助功能树由屏幕阅读器等辅助技术使用。如果跳过 DOM,那么许多用户突然就无法访问 web 内容。我们可能会看到朝着这个方向发展,但首先需要进行大量的标准化和实现工作。

          2017 年 3 月 7 日 上午 9:57

          1. Mike

            我希望所有这些问题都能尽快得到解决(在我看来,这应该是重中之重)。只要我们继续依赖 DOM / HTML / CSS 等来进行 web 编程,它就会一直是一个彻底的混乱(当然,这是我的观点)。

            性能很好,但拥有从头开始设计和优化的框架来用于用户界面创建至关重要。DOM / HTML / CSS 等技术是毫无必要复杂性的典型代表,据我了解,它们从未被设计成今天所做的事情,从表面上看,似乎有无穷无尽的不同框架旨在帮助简化 web 用户界面的创建,这一点就证明了这一点。我想要更好的方法来创建、维护和增强我编写的代码,而不必像今天进行 web 开发时那样完全使用 hack。

            再说一次,我对 web 开发的经验不多(我尽量避免使用它),因此如果我说了一些没有意义的话,请您原谅。

            2017 年 3 月 7 日 上午 11:26

          2. Mike

            嗨,Lin,

            抱歉一直在这个问题上纠缠,但我想知道你是否愿意回答另一个与这个主题相关的问题。

            使用当前版本的 Webassembly,是否可以为某人创建一个编译器,该编译器可以获取来自 *桌面* 应用程序(使用 Microsoft WPF 或 Delphi 开发的丰富用户界面)的代码,并生成 Webassembly 代码,该代码可以在浏览器中模拟应用程序的行为?

            我知道这可能不是最佳选择,甚至可能不被鼓励,但我很好奇这是否至少在理论上是可能的(无需付出巨大努力)。

            我猜答案是肯定的,因为我已经看到了基于 Webassembly 的浏览器游戏,所以如果你可以在浏览器上绘制精美的图形,那么绘制典型的文本框、组合框等应该相对容易。这里关键的想法是能够跳过整个浏览器技术混乱(JavaScript、DOM / HTML / CSS 等),或者至少,它们的用法应该绝对最小化,并对开发人员透明。

            谢谢。

            2017 年 3 月 8 日 上午 9:13

  3. Skatox

    太棒了!我甚至可以使用它向我的学生解释。干得好!

    2017 年 2 月 28 日 下午 5:28

  4. Dave

    非常有趣的文章!快速提问,你用什么软件来制作你的图表?我真的很喜欢手绘风格 :)

    2017 年 2 月 28 日 下午 9:26

    1. Lin Clark

      谢谢!我使用 Pixelmator 和 Wacom Cintiq 平板电脑。

      2017 年 3 月 1 日 上午 6:20

  5. Chris

    写得非常棒的一组文章!非常易于理解,内容扎实。

    我认为你错过了一个要点:虽然 wasm 没有运行时支持垃圾收集器之类的奢侈功能,但据我所知,没有什么可以阻止任何人编写一个。wasm 的真正底层技巧颠覆了 web 的编程模型:它不是选择退出(优化退出!)高级功能,而是选择加入你真正想要的功能。通过将执行范围缩减到简单的机器,就有空间在该基础上构建不同的方法。

    Rust 的静态分析模型会击败垃圾收集器吗?有人会想出其他方法吗?谁知道呢?希望无论它是什么,它都能编译成 wasm,并像今天的 web 应用程序一样以按需的方式交付。

    我期待着看到它的结果。

    2017 年 3 月 1 日 下午 12:09

  6. 匿名

    tl;dr WebAssembly 是一款伪装成新技术的超级压缩器

    2017 年 3 月 1 日 下午 12:10

    1. Lin Clark

      我不同意这是 TL;DR。压缩只减少了花费在获取和解析上的时间。它不会减少花费在其他类别上的时间。希望这些图表能清楚地说明 WASM 减少了花费在所有 6 个类别上的时间。

      2017 年 3 月 1 日 下午 1:20

  7. alexander

    很棒的文章!
    “执行速度非常慢”可能应该改为“执行速度很慢” ;)。

    2017 年 3 月 1 日 下午 12:16

    1. Lin Clark

      谢谢你的赞美和指正 :)

      2017 年 3 月 1 日 下午 1:14

  8. Phuong Nguyen

    这是一项非常棒且激动人心的新技术,我迫不及待地想等到它稳定下来,以便尝试在我的 web 应用程序中使用它 :)

    2017 年 3 月 2 日 上午 6:46

  9. Reddy

    第二段“获取”下有一处拼写错误,应该是“比……更紧凑”。

    总的来说,这组文章写得很好,它确实帮助我理解了 Web Assembly 及其对 Web 未来意味着什么。

    2017 年 3 月 5 日 上午 8:04

    1. Lin Clark

      谢谢!感谢你发现的拼写错误。

      2017 年 3 月 5 日 上午 8:15

  10. Zonr

    多么迷人的帖子!我想在结论中的以下要点中添加一条评论。

    “不需要重新优化……”

    除了“反优化”之外,JIT 中实际上还有另一种类型的“重新优化”,称为“自适应优化”[1],它将当前的已编译代码“提升”为更好的代码。例如,在运行一段时间后,我们有信心一个函数是热点。然后,我们可以投入更多时间对其进行一些提前优化(例如,矢量化),并期望这些努力会得到回报。无论是否采用 WebAssembly,这都可能发生。

    [1]: https://en.wikipedia.org/wiki/Adaptive_optimization

    2017 年 3 月 5 日 下午 7:38

  11. John Lehew

    好文章。WebAssembly 将成为下一件大事,很高兴看到它。尤其是解析和编译大型框架和第三方库所花费的时间会累积起来,这是一个很大的问题。我很高兴看到这个问题得到了解决。

    当重定向到新页面时,WebAssembly 模块和分配的内存是否始终被清除?如果是这样,这将解决许多垃圾回收问题,因为内存会在加载下一个页面之前被清除。SPA 应用可以调用一个函数来清除所有内存和库,作为执行 GC 的一种方式吗?

    是否有计划在浏览器中包含和安装标准的 WebAssembly 库?例如,如果 Bootstrap 将其库编译成 WebAssembly,它可以作为扩展库安装在浏览器中,并在网页中按需访问吗?或者浏览器只是从 CDN 缓存 wasm 库?

    2017 年 3 月 9 日 上午 1:04

本文评论已关闭。