超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

是什么使 WebAssembly 很快? - What makes WebAssembly fast?

原文:What makes WebAssembly fast?

本文译自Lin Clark 关于 WebAssembly 的卡通介绍系列,渣翻译,因此附上英文原文

In the last article, I explained that programming with WebAssembly or JavaScript is not an either/or choice. We don’t expect that too many developers will be writing full WebAssembly code bases.

上一篇文章中,我说过使用 WebAssembly 或 JavaScript 进行编程不是二选一的选择。我们不希望太多的开发人员直接编写完整的 WebAssembly 代码库。

So developers don’t need to choose between WebAssembly and JavaScript for their applications. However, we do expect that developers will swap out parts of their JavaScript code for WebAssembly.

所以开发人员不需要为 Web 应用程序选择 WebAssembly 和 JavaScript。 但是,我们期望开发人员将部分 JavaScript 代码改为使用 WebAssembly 的。

For example, the team working on React could replace their reconciler code (aka the virtual DOM) with a WebAssembly version. People who use React wouldn’t have to do anything… their apps would work exactly as before, except they’d get the benefits of WebAssembly.

例如,在 React 上工作的团队可以使用 WebAssembly 版本替换他们的调节器代码(也称为虚拟DOM)。 使用 React 的人不需要做任何事情,他们的应用程序将会像以前那样正常工作,除了他们可以从 WebAssembly 获益。

The reason developers like those on the React team would make this swap is because WebAssembly is faster. But what makes it faster?

开发人员像 React 团队会切换到 WebAssembly 是因为其速度更快。但是什么使它更快?

现在,JavaScript 性能如何?

Before we can understand the differences in performance between JavaScript and WebAssembly, we need to understand the work that the JS engine does.

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

This diagram gives a rough picture of what the start-up performance of an application might look like today.

这张图粗略展示了现在的应用程序的启动性能的大概样子。

The time that the JS engine spends doing any one of these tasks depends on the JavaScript the page uses. This diagram isn’t meant to represent precise performance numbers. Instead, it’s meant to provide a high-level model of how performance for the same functionality would be different in JS vs WebAssembly.

JS 引擎处理这些任务中的时间取决于页面使用的 JavaScript。该图并不为了表示精确的性能数字。相反,提供一个 JS 和 WebAssembly 中相同功能的性能不同的高级模型。

Each bar shows the time spent doing a particular task.

  • Parsing — the time it takes to process the source code into something that the interpreter can run.
  • Compiling + optimizing — the time that is spent in the baseline compiler and optimizing compiler. Some of the optimizing compiler’s work is not on the main thread, so it is not included here.
  • Re-optimizing — the time the JIT spends readjusting when its assumptions have failed, both re-optimizing code and bailing out of optimized code back to the baseline code.
  • Execution — the time it takes to run the code.
  • Garbage collection — the time spent cleaning up memory.

每个横条显示在特定任务上花费的时间:

  • 解析(Parsing):将源代码处理为解释器可以运行的内容所需的时间。
  • 编译+优化(Compiling + optimizing):在基线编译器和优化编译器中花费的时间。一些优化编译器的工作不在主线程,所以它不包括在这里。
  • 重新优化(Re-optimizing):JIT 在其假设失败时重新调整花费的时间,包括将优化代码重新优化和回滚优化代码到基准代码。
  • 执行(Execution):运行代码所需的时间。
  • 垃圾回收(Garbage collection):清理内存的时间。

One important thing to note: these tasks don’t happen in discrete chunks or in a particular sequence. Instead, they will be interleaved. A little bit of parsing will happen, then some execution, then some compiling, then some more parsing, then some more execution, etc.

一个要注意的重要内容:这些任务不是肚子执行或者以特定的顺序执行。相反,它们将被交错执行:一些解析发生,然后执行,然后编译,然后更多的解析,再然后更多的执行等。

The performance this breakdown brings is a big improvement from the early days of JavaScript, which would have looked more like this:

这是对早期的 JavaScript 的性能一个重大的改进,早期的 JavaScript 更像这样:

In the beginning, when it was just an interpreter running the JavaScript, execution was pretty slow. When JITs were introduced, it drastically sped up execution time.

一开始,当它只是一个运行 JavaScript 的解释器时,执行速度相当缓慢。当 JIT 引入时,它大大加快了执行时间。

The tradeoff is the overhead of monitoring and compiling the code. If JavaScript developers kept writing JavaScript in the same way that they did then, the parse and compile times would be tiny. But the improved performance led developers to create larger JavaScript applications.

通过权衡监视和编译代码的开销。如果 JavaScript 开发人员按照他们所做的相同的方式编写 JavaScript,那么解析和编译时间将会很小。但改进的性能使开发人员能够创建更大的JavaScript 应用程序。

This means there’s still room for improvement.

这意味着还有改进的余地。

WebAssembly 比较起来如何?

Here’s an approximation of how WebAssembly would compare for a typical web application.

这是 WebAssembly 和典型的 Web 应用程序的近似比较结果。

There are slight variations between browsers in how they handle all of these phases. I’m using SpiderMonkey as my model here.

浏览器在处理所有这些阶段的过程中有轻微的变化。我在这里使用 SpiderMonkey (SpiderMonkey 是 Mozilla 项目的一部分,是一个用 C 语言实现的 JavaScript 脚本引擎)作为我的模型。

获取文件(Fetching)

This isn’t shown in the diagram, but one thing that takes up time is simply fetching the file from the server.

这一条并未在图中显示,但是从服务器获取文件需要花费一些时间。

Because WebAssembly is more compact than JavaScript, fetching it is faster. Even though compaction algorithms can significantly reduce the size of a JavaScript bundle, the compressed binary representation of WebAssembly is still smaller.

因为 WebAssembly 比 JavaScript 更紧凑,所以获取它更快。即使压缩算法可以显着减小 JavaScript 包的大小,WebAssembly 的二进制压缩表示仍然较小。

This means it takes less time to transfer it between the server and the client. This is especially true over slow networks.

这意味着在服务器和客户端之间传输它所需的时间更少,特别是在慢速网络中更加明显。

解析(Parsing)

Once it reaches the browser, JavaScript source gets parsed into an Abstract Syntax Tree.

一旦文件下载到浏览器,JavaScript 源码将被解析为抽象语法树(AST)

Browsers often do this lazily, only parsing what they really need to at first and just creating stubs for functions which haven’t been called yet.

浏览器经常这样做,只是解析它们期初真正需要的内容,为尚未被调用的功能只是创建存根(stubs)

From there, the AST is converted to an intermediate representation (called bytecode) that is specific to that JS engine.

然后,AST 被转换为特定于该 JS 引擎的中间表示(称为字节码)。

In contrast, WebAssembly doesn’t need to go through this transformation because it is already an intermediate representation. It just needs to be decoded and validated to make sure there aren’t any errors in it.

与此相比,WebAssembly 不需要经历这种转换,因为它已经是一个中间表示,它只需要被解码和验证,以确保没有任何错误。

编译+优化(Compiling + optimizing)

As I explained in the article about the JIT, JavaScript is compiled during the execution of the code. Depending on what types are used at runtime, multiple versions of the same code may need to be compiled.

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

Different browsers handle compiling WebAssembly differently. Some browsers do a baseline compilation of WebAssembly before starting to execute it, and others use a JIT.

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

Either way, the WebAssembly starts off much closer to machine code. For example, the types are part of the program. This is faster for a few reasons:

  1. The compiler doesn’t have to spend time running the code to observe what types are being used before it starts compiling optimized code.
  2. The compiler doesn’t have to compile different versions of the same code based on those different types it observes.
  3. More optimizations have already been done ahead of time in LLVM. So less work is needed to compile and optimize it.

无论使用哪种方式,WebAssembly 都是从更接近机器代码的方式开始的。例如,这些类型是程序的一部分。这是因为以下几个原因:

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

重新优化(Reoptimizing)

Sometimes the JIT has to throw out an optimized version of the code and retry it.

有时 JIT 必须抛出一个优化版本的代码并重试。

This happens when assumptions that the JIT makes based on running code turn out to be incorrect. For example, deoptimization happens when the variables coming into a loop are different than they were in previous iterations, or when a new function is inserted in the prototype chain.

特别是当基于运行代码的 JIT 的假设运行的结果不正确时,会发生这种情况。 例如,当进入循环的变量与前一个迭代不同时,或者在原型链中插入新函数时,会发生 去优化(deoptimization)

There are two costs to deoptimization. First, it takes some time to bail out of the optimized code and go back to the baseline version. Second, if that function is still being called a lot, the JIT may decide to send it through the optimizing compiler again, so there’s the cost of compiling it a second time.

去优化有两个代价。 首先,需要一些时间才能取消优化代码,返回到基准版本。 第二,如果这个功能还是被调用很多,JIT 可能会决定再次使优化编译器处理它,所以第二次有编译成本。

In WebAssembly, things like types are explicit, so the JIT doesn’t need to make assumptions about types based on data it gathers during runtime. This means it doesn’t have to go through reoptimization cycles.

在 WebAssembly 中,诸如类型之类的东西是显式的,所以 JIT 不需要根据运行时收集的数据对类型进行假设。这意味着它不必经过优化周期。

执行(Executing)

It is possible to write JavaScript that executes performantly. To do it, you need to know about the optimizations that the JIT makes. For example, you need to know how to write code so that the compiler can type specialize it, as explained in the article on the JIT.

编写执行性能好的 JavaScript 是可能的。要做到这一点,你需要了解 JIT 所做的优化。比如,你需要知道如何编写代码,以便编译器可以将其特化,正如 JIT 的文章所提到的那样。

However, most developers don’t know about JIT internals. Even for those developers who do know about JIT internals, it can be hard to hit the sweet spot. Many coding patterns that people use to make their code more readable (such as abstracting common tasks into functions that work across types) get in the way of the compiler when it’s trying to optimize the code.

然而,大多数开发人员不了解 JIT 内部。即使对于了解 JIT 内部部分的开发人员,也很难做到最佳。许多编码模式,人们用来使他们的代码更易于阅读(例如将常见任务抽象为跨类型工作的函数),在编译器试图优化代码时,会阻碍编译器的优化。

Plus, the optimizations a JIT uses are different between browsers, so coding to the internals of one browser can make your code less performant in another.

此外,JIT 的优化在不同浏览器是不同的,因此对一个浏览器的优化的代码可能在另一个浏览器中性能更低。

Because of this, executing code in WebAssembly is generally faster. Many of the optimizations that JITs make to JavaScript (such as type specialization) just aren’t necessary with WebAssembly.

因此,在 WebAssembly 中执行代码通常更快。 JIT 对 JavaScript 的许多优化(如类型专业化)在 WebAssembly 中不需要。

In addition, WebAssembly was designed as a compiler target. This means it was designed for compilers to generate, and not for human programmers to write.

此外,WebAssembly 被设计为编译器目标。 这意味着它是为编译器而设计的,而不是为了编程人员编写。

Since human programmers don’t need to program it directly, WebAssembly can provide a set of instructions that are more ideal for machines. Depending on what kind of work your code is doing, these instructions run anywhere from 10% to 800% faster.

由于人类程序员不需要直接编程,WebAssembly 可以提供一组更适合机器的指令。 根据你的代码正在做什么样的工作,这些指令的运行速度可以快 10% 到 800%。

垃圾回收(Garbage collection)

In JavaScript, the developer doesn’t have to worry about clearing out old variables from memory when they aren’t needed anymore. Instead, the JS engine does that automatically using something called a garbage collector.

在 JavaScript,开发人员不必考虑在不再需要时从内存中清除旧的变量。相反,JS 引擎会自动执行垃圾收集器。

This can be a problem if you want predictable performance, though. You don’t control when the garbage collector does its work, so it may come at an inconvenient time. Most browsers have gotten pretty good at scheduling it, but it’s still overhead that can get in the way of your code’s execution.

这可能是一个问题,如果你想要可预测的性能,但是。你不能控制垃圾收集器的工作,所以垃圾回收可能会在不方便的时候进行。大多数浏览器已经很好的安排了它,但它仍然是开销,可能阻碍你的代码的执行。

At least for now, WebAssembly does not support garbage collection at all. Memory is managed manually (as it is in languages like C and C++). While this can make programming more difficult for the developer, it does also make performance more consistent.

至少现在,WebAssembly 根本不支持垃圾自动回收。 内存被手动管理(如 C 和 C++语言)。 虽然这可以使开发人员的编程变得更加困难,但它也使性能更加一致。

总结

WebAssembly is faster than JavaScript in many cases because:

  • fetching WebAssembly takes less time because it is more compact than JavaScript, even when compressed.
  • decoding WebAssembly takes less time than parsing JavaScript.
  • compiling and optimizing takes less time because WebAssembly is closer to machine code than JavaScript and already has gone through optimization on the server side.
  • reoptimizing doesn’t need to happen because WebAssembly has types and other information built in, so the JS engine doesn’t need to speculate when it optimizes the way it does with JavaScript.
  • executing often takes less time because there are fewer compiler tricks and gotchas that the developer needs to know to write consistently performant code, plus WebAssembly’s set of instructions are more ideal for machines.
  • garbage collection is not required since the memory is managed manually.

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

  • 获取 WebAssembly 需要较少的时间,因为它比 JavaScript 更紧凑,即使在 JavaScript 压缩时也是如此。
  • 解码 WebAssembly 比解析 JavaScript 要花费更少的时间。
  • 编译和优化需要更少的时间,因为 WebAssembly 比 JavaScript 更接近机器代码,并且已经在服务器端进行了优化。
  • 重新优化不会发生,因为 WebAssembly 具有内置的类型和其他信息,所以 JS 引擎不需要像使用 JavaScript 的方式推测何时对其优化。
  • 执行通常需要更少的时间,因为开发人员需要知道编写一贯性能代码的编译器技巧和问题较少,而 WebAssembly 的一组指令对于机器更为理想。
  • 不需要垃圾收集,因为手动管理内存。

This is why, in many cases, WebAssembly will outperform JavaScript when doing the same task.

这就是为什么在许多情况下,在执行相同任务时,WebAssembly 将胜过 JavaScript。

There are some cases where WebAssembly doesn’t perform as well as expected, and there are also some changes on the horizon that will make it faster. I’ll cover those in the next article.

在某些情况下,WebAssembly 不像预期的那样执行,而且未来,还有一些改变将使其更快。我将在下一篇文章中介绍。