说明
图解 Google V8 学习笔记
什么是字节码?
字节码(Byte-code)是一种包含执行程序、由一序列 op 代码/数据对组成的二进制文件。字节码是一种中间码,它比机器码更抽象。它经常被看作是包含一个执行程序的二进制文件,更像一个对象模型。字节码被这样叫是因为通常每个 opcode 是一字节长,但是指令码的长度是变化的。每个指令有从 0 到 255(或十六进制的: 00 到FF)的一字节操作码,被参数例如寄存器或内存地址跟随。
所谓字节码,是指编译过程中的中间代码。
字节码有两个作用:
解释器可以直接解释执行字节码 ;
优化编译器可以将字节码编译为二进制代码,然后再执行二进制机器代码。
早期 V8 执行流水线:直接将 JavaScript 代码编译成机器代码。
- 基线编译器,它负责将 JavaScript 代码编译为没有优化过的机器代码。
- 优化编译器,它负责将一些热点代码(执行频繁的代码)优化为执行效率更高的机器代码。
首先,V8 会将一段 JavaScript 代码转换为抽象语法树 (AST)。
接下来基线编译器会将抽象语法树编译为未优化过的机器代码,然后 V8 直接执行这些未优化过的机器代码。
在执行未优化的二进制代码过程中,如果 V8 检测到某段代码重复执行的概率过高,那么 V8 会将该段代码标记为 HOT,标记为 HOT 的代码会被优化编译器优化成执行效率高的二进制代码,然后就执行该段优化过的二进制代码。
不过如果优化过的二进制代码并不能满足当前代码的执行,这也就意味着优化失败,V8 则会执行反优化操作。
为什么现在使用了字节码 + 解释器 + 编译器方式,抛弃了直接将 JavaScript 代码编译为二进制代码的方式,尽管机器代码的执行性能非常高效,出于什么原因考虑?
时间问题:编译时间过久,影响代码启动速度;
空间问题:缓存编译后的二进制代码占用更多的内存。
机器代码缓存
通过把二进制代码保存在内存中来消除冗余的编译,重用它们完成后续的调用,这样就省去了再次编译的时间。实践表明,在浏览器中采用了二进制代码缓存的方式,初始加载时分析和编译的时间缩短了 20%~40%。
V8 使用了两种代码缓存策略:
内存缓存(in-memory cache):V8 第一次执行一段代码时,会编译源 JavaScript 代码,并将编译后的二进制代码缓存在内存中。
硬盘缓存:即便关闭了浏览器,下次重新打开浏览器再次执行相同代码时,也可以直接重复使用编译好的二进制代码。
在早期,Chrome 用了这两种代码缓存的策略来提升 JavaScript 代码的执行速度,以牺牲存储空间来换取执行速度。
JavaScript 代码和二进制代码:
二进制代码所占用的内存空间是 JavaScript 代码的几千倍,V8 过度占用内存,会导致 Web 应用的速度大大降低。
为了解决缓存的二进制机器代码占用过多内存的问题,早期的 Chrome 并没有缓存函数内部的二进制代码,只是缓存了顶层次的二进制代码,采用了惰性编译,其实惰性编译除了能提升 JavaScript 启动速度,还可以解决部分内存占用的问题。
缺陷:如果浏览器只缓存顶层代码,那么闭包模块中的代码将无法被缓存,而对于高度工程化的模块来说,这种模块式的处理方式到处都是,这就导致了一些关键代码没有办法被缓存。
因此,V8 团队对早期的 V8 架构进行了非常大的重构,具体地讲,抛弃之前的基线编译器和优化编译器,引入了字节码、解释器和新的优化编译器。
字节码降低了内存占用
为什么通过引入字节码就能降低 V8 在执行时的内存占用呢?
字节码虽然占用的空间比原始的 JavaScript 多,但是相较于机器代码,字节码占用的空间远小于二进制代码,所以浏览器就可以实现缓存所有的字节码,而不是仅仅缓存顶层的字节码。
虽然采用字节码在执行速度上稍慢于机器代码,但是整体上权衡利弊,采用字节码也许是最优解。因为采用字节码除了降低内存之外,还提升了代码的启动速度,并降低了代码的复杂度,而牺牲的仅仅是一点执行效率。
字节码如何提升代码启动速度?
启动 JavaScript 代码的流程图:
- 生成机器代码比生成字节码需要花费更久的时间
- 直接执行机器代码却比解释执行字节码要更高效
V8 在使用的模型:
- V8 的解释器叫
Ignition
,(就原始字节码执行速度而言)是所有引擎中最快的解释器。 - V8 的优化编译器名为
TurboFan
,最终由它生成高度优化的机器码。
字节码如何降低代码的复杂度?
早期的 V8 代码,无论是基线编译器还是优化编译器,它们都是基于 AST 抽象语法树来将代码转换为机器码的,不同架构的处理器非常之多
这意味着基线编译器和优化编译器要针对不同的体系的 CPU 编写不同的代码,这会大大增加代码量。
引入了字节码,就可以统一将字节码转换为不同平台的二进制代码:
因为字节码的执行过程和 CPU 执行二进制代码的过程类似,相似的执行流程,那么将字节码转换为不同架构的二进制代码的工作量也会大大降低,这就降低了转换底层代码的工作量。字节码是平台无关的,机器码针对不同的平台都是不一样的。
总的来说字节码的优势有如下三点:
解决启动问题:生成字节码的时间很短;
解决空间问题:字节码占用内存不多,缓存字节码会大大降低内存的使用;
代码架构清晰:采用字节码,可以简化程序的复杂度,使得 V8 移植到不同的 CPU 架构平台更加容易。