V8编译流水线-惰性解析/字节码

963 阅读5分钟

V8在编译JS的过程中,并不会将所有的JS解析成中间代码,主要原因如下:

  • 如果全部解析和编译,会增加编译时间,延长用户的等待时间。
  • 解析完成的机器码会放在内存中,如果全部解析完成,内存占用量会很大。

基于以上原因,所以JS虚拟机实现了惰性解析。惰性解析就是解析器在解析的过程中,如果遇到函数声明,就会跳过函数内部的代码,仅仅生成顶层代码的AST和字节码。其中闭包会让V8的解析过程变的复杂。

1. 惰性解析

1.1 闭包

闭包的三个特性:

  • JS语言允许在函数内部定义新的函数。
  • 可以在内部函数中访问父函数中定义的变量。变量的查找按照词法作用域链来查找。
  • 因为函数是一等公民,所以函数可以作为返回值。
function foo() {
	let a = 1
    return function bar(b) {
    	return a + b
    }
}

当函数引用闭包时,函数执行完成,会从栈中弹出,函数中的变量和作用域会被销毁,但是闭包中引用的父函数变量会依然保存到内存中,没有被销毁掉。处理这个任务的模块叫预解析器。

1.2 预解析器

V8引入预解析器,当解析顶层代码的时候,如果遇到一个函数,预解析器并不会直接跳过该函数,而是对该函数做一次快速的预解析。

预解析的作用:

  • 检查语法错误
  • 检查函数内部是否引用了外部变量,如果引用了外部变量,预解析器会将栈中的变量赋值到堆中,在下次执行该函数时,直接使用堆中的引用。

1.3 总结

V8的惰性解析是指解析器在解析过程中,如果遇到函数声明,则跳过函数内部代码,仅生成顶层代码的AST和字节码。利用惰性解析可以加速JS的编译速度和节约内存。

V8解析函数的时候,会使用预解析器快速解析函数内部是否包含了外部函数声明的变量,如果引用了,就会将该变量复制存放到堆中,即使当前函数执行完也不会释放该变量,从而实现闭包的功能。

2. 字节码

字节码就是编译过程中的中间代码,是机器代码的抽象,V8中的字节码有两个作用:

  • 解释器直接解释执行字节码。
  • 优化编译器可以将字节码编译为二进制代码,然后执行二进制代码。

2.1 机器代码缓存

在浏览器执行JS之前,早期的V8会先编译JS为未优化过的二进制机器代码,然后执行二进制代码。如果浏览器再次打开相同的页面,而且JS没有修改过,此时再编译就会浪费CPU的资源,所以浏览器引入二进制代码缓存,把编译后的二进制代码缓存在内存中,省去编译的时间。

V8使用两种策略来缓存生成的代码:

  • V8第一次执行代码,编译源JS代码,并将编译后的二进制代码缓存在内存中,这种方式叫内存缓存。然后通过JS源文件的字符串在内存中查找编译后的二进制代码。当执行时,会先去内存中查找是否编译过。
  • V8除了内存缓存外,还将代码缓存到硬盘上,这样即使关闭浏览器,下次重开浏览器并执行时,也可以重复使用编译好的JS代码。

2.2 字节码降低内存占用

采用缓存是典型的以空间换时间的策略,牺牲存储空间换取执行速度。JS编译成二进制后,二进制文件十分占内存,V8为了提升启动速度,采用惰性编译,这样能解决部分内存占用的问题。但是如果函数中有较多的闭包,会导致闭包中的代码无法缓存。所以只缓存顶层代码是不完美的,所以V8引入字节码来继续降低V8执行时的内存占用。

上图看出,字节码的占用空间远小于二进制代码,所以可以针对字节码来进行操作优化。同时浏览器缓存所有的字节码。虽然字节码的执行速度稍慢机器码,但是采用字节码能有效降低内存,并提高代码启动速度。

2.3 字节码如何提高代码启动速度

JS代码启动流程图:

解释器可以快速生成字节码,优化编译器虽然需要更长的时间来处理,但是最终会产生更高效的机器码,这就是V8在使用的模型,它的解释器叫Ignition,是所有引擎中最快的解释器。V8的优化编译器叫TurboFan,由它生成高度优化的机器码。

2.4 字节码如何降低代码复杂度

引入字节码,可以统一将字节码转换为不同平台的二进制代码(机器码),不用再去针对不同的CPU去做兼容。而且字节码的执行过程跟CUP执行二进制代码类似,也降低了转换底层代码的工作量。

2.5 总结

早期的V8在启动时,直接将JS源码编译为二进制的机器码,但是会产生两个问题:

  • 时间问题:编译时间过久。
  • 空间问题:缓存编译后的二进制代码会占用更多的内存。

为了解决上述问题,V8进行了重构,引入了中间的字节码。字节码优势有三点:

  • 解决启动时间问题:生成字节码的时间会更短。
  • 解决空间问题:字节码占用内存比二进制的机器码占用内存更少,并且使用缓存会大大降低内存的使用。
  • 代码架构清晰:简化程序复杂度,使得V8移植到不同的CUP架构平台更加容易。