V8引擎如何运行JS

前言

V8引擎如何编译和优化JS的

V8引擎如何运行JS_第1张图片
image.png

什么是V8

官网: https://v8.dev/
V8是一个接收JavaScript代码,编译代码然后执行的C++程序,编译后的代码可以在多种操作系统多种处理器上运行。
Chrome浏览器JS的引擎是V8
Nodejs的运行环境是V8引擎
electron的底层引擎也是V8

V8引擎如何运行JS_第2张图片
image.png

V8主要负责的工作

编译和执行JS代码、处理调用栈
内存的分配、垃圾的回收

早期的V8引擎是如何编译JS代码

V8引擎曾在2017年做了一次大的架构调整
追本溯源,了解一下早期的V8引擎是如何编译JS代码的
一般来说,大部分JS引擎在编译和执行代码都会用到三个重要的文件
重要组件: 解析器、解释器和编译器

1. 解析器负责将源代码解析成抽象语法树AST

解释器负责将AST解释成字节码bytecode,同时解释器也有直接解释执行bytecode的能力

2. 编译器负责编译出运行更加高效的机器代码

在V8早期5.9版本之前,V8引擎没有解释器,却有两个编译器,编译流程如下:

JS由解析器解析后,生成AST抽象语法树,然后由Full-codegen编译器直接使用AST来编译出机器代码而不进行任何中间转换,Full-codefen编译器也被称为基准编译器,因为它生成的是一个基准的未被优化的机器代码,这样做的好处是当第一次执行JS的时候,就是直接使用了高效的机器代码,因为没有中间的字节码产生,所以就不需要解释器,当代码运行一段时间后,V8引擎中的分析线程,收集了足够的数据来帮助另一个编译器Crankshaft来做代码优化,然后需要优化的源码重新解析生成AST,然后Crankshaft使用生成好的AST再生成优化后的机器代码来提升运行的效率,所以Crankshaft的编译器又被称为优化编译器。这样的设计初衷是好的,减少抽象语法树到字节码的转换时间,提高外部浏览器中JS的执行的性能,但是这样的架构设计也带来了不少的问题:
1. 生成的机器码会占用大量的内存

这对于大内存的电脑还好说,但对于早期的安卓低内存的设备,基本是不能承受的,并且有些代码仅仅执行一次,没有必要直接生成机器码

2. 缺少中间层机器码,无法实现一些优化策略,导致V8引擎性能提升缓慢
3. 之前的编译器无法很好的支持和优化JS的新语法特性
因此,V8团队为了解决这些问题,用了三年半时间开发了一套新的V8架构,V8团队称之为设计顶峰的新架构:
image.png

新的V8架构是怎样的?

语法树的解析基本还是保持一致的;但在获得抽象语法树之后,V8引擎加入了解释器lgnition,语法树通过解释器lgnition生成了bytecode字节码,此时AST就被清除掉了,释放内存空间,生成的bytecode直接被解释器执行,同时生成的bytecode将作为基准执行模型,字节码更加简洁,生成的bytecode大小相当于等效的基准机器代码的25%到50%,在代码不断的运行过程中,解释器收集到了很多可以用来优化代码的信息,比如变量的类型,哪些函数执行的频率较高,这些信息被发送给编译器,V8引擎新的编译器TurboFan会根据这些信息和字节码,来编译出经过优化的机器代码。
V8引擎如何运行JS_第3张图片
image.png

V8引擎在处理JS过程中的一些优化策略:

1.函数只声明未被调用,不会被解析生成AST,也就不会生成字节码
2.函数只被调用一次,bytecode直接被解释执行,TureboFan不会进行优化编译
3.函数被调用多次,可能会被标记为热点函数,可能会被编译成机器代码。当lgnition解释器收集的类型信息确定后,这是TurboFan则会将bytecode编译为优化后的机器代码,以提高代码的执行性能,最后执行这个函数时,就直接运行优化后的机器代码。


V8引擎如何运行JS_第4张图片
image.png
所以整体来说,就是处于一个运行字节码和优化的机器代码共存的一个状态,随着JS源码不断的被执行,会有更多的源码被标记为热点代码,就会产生更多的机器代码。

逆向还原(deoptimization)

在某些情况下,优化后的机器码可能会被逆向还原称字节码,这个过程叫做deoptimization,这是因为JavaScript是一个动态语言,会导致一个lgnition收集到的信息是错误的

例如:有一个sum函数,在函数声明时,JS引擎并不知道参数x,y是什么类型,但在后面多次调用中,传入的x,y都是整型,sum函数被识别为热点函数,解释器将收集到的类型信息和该函数对应的字节码发送给编译器,于是编译器生成的优化后的机器代码中就假定了sum函数的参数x,y都是整型,之后遇到该函数的调用就直接使用运行更快的机器代码,如果此时调用sum函数传入了字符串,机器代码不知道如何处理字符串的参数,于是就需要进行deoptimization回退字节码,由解释器来解释执行。
因此我们尽量不要把一个变量的类型变来变去,对传入函数的参数的类型也是最好保持固定,否则会给V8引擎损失一定的性能
function sum(x, y) {
  return x + y
}
V8引擎如何运行JS_第5张图片
image.png

新V8架构的好处

不需要一开始直接编译成机器码,而是生成了中间层的字节码,字节码的生成速度远远大于机器码的,所以网页初始化解析执行JS的时间缩短了,网页就可以更快的onload
在生成的优化机器代码时,不需要从源码重新编译,而使用字节码,并且当需要deoptimization时,只需要回归到中间层的字节码解释执行就可以了
新的架构在性能上带来了很大的提升


image.png

参考学习视频: https://www.bilibili.com/video/BV1zV411z7RX

你可能感兴趣的:(V8引擎如何运行JS)