跳到主要内容

启动 Ignition 解释器

· 阅读需 4 分钟
Ross McIlroy, V8 Ignition 初学者

V8 和其他现代 JavaScript 引擎通过 即时(JIT)编译 将脚本转换为本机机器代码以获得执行速度,而这通常是在执行前立即进行的。代码最初由基础编译器快速生成非优化的机器代码,在运行时进行分析,并根据需要动态使用更高级的优化编译器进行重编译以实现更优性能。在 V8 中,这种脚本执行管道包含各种特殊情况和条件,因而需要复杂的机制在基础编译器和两个优化编译器 Crankshaft 与 TurboFan 之间切换。

这种方法的问题之一(除了架构复杂性外)在于,即使代码仅执行一次,JIT 机器代码也可能占用大量内存。为了减少这一开销,V8 团队开发了一种新的 JavaScript 解释器,名为 Ignition,能够替代 V8 的基础编译器,用较少的内存开销执行代码,并为更简化的脚本执行管道铺平道路。

使用 Ignition,V8 将 JavaScript 函数编译为精简的字节码,其大小仅为相应基础机器代码的 50% 到 25%。这些字节码随后由高性能解释器执行,其在实际网站上的执行速度接近 V8 现有基础编译器生成的代码。

在 Chrome 53 中,Ignition 将在内存有限(512 MB 或以下)的 Android 设备上启用,这些设备对节省内存的需求尤为强烈。早期实验结果显示,Ignition 能将每个 Chrome 标签页的内存减少约 5%。

启用 Ignition 的 V8 编译管线

详细信息

在构建 Ignition 字节码解释器时,团队考虑了多种潜在实现方法。一个用 C++ 编写的传统解释器无法与 V8 的其他生成代码高效交互。而另一个替代方案是用汇编代码手工编码解释器,但鉴于 V8 支持九个架构端口,这将需要大量的工程成本。

因此,我们选择了一种结合 TurboFan 优势的方法,这是我们新优化编译器,已经针对与 V8 运行时及其他生成代码的最佳交互进行了优化。Ignition 解释器使用 TurboFan 的低级、架构无关宏组装指令为每个操作码生成字节码处理程序。TurboFan 将这些指令编译为目标架构,同时完成低级指令选择和机器寄存器分配。这产生了高度优化的解释器代码,可执行字节码指令并以低开销方式与 V8 虚拟机其他部分交互,同时仅需在代码库中增加最少的新组件。

Ignition 是寄存器机器,每个字节码以显式寄存器操作数指定输入和输出,而不是像堆栈机器那样每个字节码会从隐式堆栈中获取输入并将输出推入堆栈。一个特殊的累加器寄存器是许多字节码的隐式输入和输出寄存器。这一设计通过避免指定具体寄存器操作数来减少字节码的大小。由于许多 JavaScript 表达式涉及由左至右的运算链条,这些运算的临时结果通常可以保留在累加器中,从而减少加载和存储显式寄存器的操作需求。

字节码在生成过程中会通过一系列内联优化阶段。这些阶段对字节码流进行简单分析,用更快的序列替代常见模式,移除一些冗余操作,并减少不必要的寄存器加载和传输数量。综合起来,这些优化进一步减少了字节码的大小并提高了性能。

关于 Ignition 实现的更多细节,请参阅我们的 BlinkOn 演讲:

未来

到目前为止,我们在Ignition上的重点是减少V8的内存开销。然而,将Ignition添加到我们的脚本执行管道中为未来带来了许多可能性。Ignition管道的设计使我们能够更聪明地决定何时执行和优化代码,以加快加载网页的速度并减少卡顿,同时使V8各个组件之间的交互更加高效。

敬请期待Ignition和V8的未来发展。