[翻译] V8引擎的解析

原文:Parsing in V8 explained函数

本文档介绍了 V8 引擎是如何解析 JavaScript 源代码的,以及咱们将改进它的计划。 ui

动机

咱们有个解析器和一个更快的预解析器(~2x),可是预解析器对大多数现代 JavaScript 无用。此外假如尚未编译了外部函数,不然咱们必须再解析一遍内部函数。 那如今的 V8 引擎何时会当即编译(eager compilation)呢?google

  1. 顶层的括号函数(function...spa

  2. 在 ( 以后的不是函数声明的内部函数3d

当咱们把一个内部函数当作非脚本级的一部分来解析的时候,是不能使用预解析器的。咱们将不会编译一个内部函数,因此若是咱们永远不先行编译函数,那么通常的 n 层嵌套函数会先被预解析,而后会有 n 次解析(以及一次编译)。或从另外一方面来看待它,考虑了如下格式的顶级“模块”:code

!function f() {
 function A() {
   function B() {...}
 }
 function C() {
   function D() {...}
 }
 ...
}(), function g() {....}()

压缩程序常常用 (function() {...})();(function(){...}))() 替换上面的代码。可是,你能够看到它没有被括号括起来,因此咱们不会在顶层运行完整的解析器。这是为何呢?由于咱们不知道这些函数会被当即调用。所以会使用更快的预解析器。可是确实须要这些函数,这就打脸了,而 V8 就不得不去再解析。预解析阶段会将整个顶层函数看一遍,包括 A, B, C, D(假如本来的 (function f() {...})() 编译过那么就会跳过本阶段)。 blog

由于顶层函数被调用了,因此引擎会去解析它。此次解析是彻底的。为何呢?由于须要作范围解析以便知道在哪里分配变量。而惟一的正确的方式是知道什么变量被引用了。而知道什么变量被引用的惟一方式是对内部函数也进行彻底的解析。因此解析/编译顶层函数会迫使引擎对 A,B,C,D 这些函数也进行彻底解析。ip

如今咱们须要调用函数 A,所以须要去编译它。为了解析它,咱们也须要知道在哪里分配变量。就像我说的:你须要知道从内部函数引用了上面。因此咱们彻底解析了 B 函数。内存

如今咱们假定预解析须要1费,解析须要2费。编译它须要另外的2费,可是咱们实际上编译的是压缩的版本,所以能够忽略。 假如如今运行 A 函数,那么将花费 3*(f + A + B) + 2*(A + B)。若是 A 将会调用 B,咱们就花费另外的2费用于 B 函数。v8

一方面,要获得一个内部函数,你须要解析一大堆。 另外一方面,顶层函数越多,解析它的成本就越高,由于你要算上解析全部嵌套函数的时间。

建议的解决方案

那么计划怎么解决呢?

  1. 预解析的同时也进行范围解析(scope resolution),这样未编译部分的花费会从2下降到1.x。

  2. 将函数的上下文内存分配信息序列化到持久存储中,以免没必要要的重解析成本。

  3. 当即编译可能会支持 ! 和 , 。

至于成本?全部懒解析函数在初始加载时的开销为1.x,若是实际使用则为3.x。.x是内部函数范围解析和序列化的额外未知成本。

当即编译的优势是,对于已知的当即执行的顶级函数,咱们能够进一步将成本从3.x降低到2。它是从顶层向内的。若是咱们决定不将预解析(eagerly parse)做为主编译工做的一部分,那么咱们能够等到它被执行,这样咱们至少能够肯定只须要支付实际使用的功能的编译成本(2解析和2编译)。

当即编译的缺点是咱们须要在解析和编译之间在内存中保持AST(Abstract syntax tree)。显著增长了使用内存的峰值。若是咱们能够预解析那些在被当即解析的函数的内部函数,状况可能会看起来好多了。即便如此,在低内存设备上,咱们应该禁用启发式的当即编译。

若是咱们序列化这些数据,那么在热启动时就完彻底全不须要去查看未使用的代码。热启动时,即便在顶层的时候也是与启发式的当即编译不相关,由于咱们将只解析/编译咱们须要的函数。 搭载了比使用的代码还要多10倍以上的页面将会当即热启动。(目前已经能够是这种状况,但它是一个有点hit-and-miss)。

相关连接:

1.Abstract syntax tree

相关文章
相关标签/搜索