V8引擎如何运行JS

·  阅读 1091
V8引擎如何运行JS

前言

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

What is V8 ?

V8 是Google的开源高性能JavaScript和WebAssembly引擎,用C++编写,它用于Chrome和Node.js等。它实现了 ECMAScript 和 WebAssembly ,并在 Windows 7 或更高版本、macOS 10.12+ 和使用 x64、IA-32、ARM 或 MIPS 处理器的 Linux 系统上运行。V8 可以独立运行,也可以嵌入任何 C++ 应用程序。

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

image.png

V8主要负责的工作

  • 编译并执行JavaScript源代码
  • 处理对象的内存分配,垃圾收集不再需要的对象

关于V8

  • stop-the-world,generational,精确垃圾收集器,是V8性能的关键。
  • JavaScript通常用于浏览器中的客户端脚本,例如浏览器的 DOM 对象,DOM 通常不是由 JavaScript引擎提供的,而是由浏览器提供的。V8 也是如此——Google Chrome提供了DOM。但是,V8提供了ECMA标准中指定的所有数据类型、运算符、对象和函数。
  • V8 使任何C++应用程序能够将自己的对象和函数暴露给 JavaScript 代码。由你决定要向 JavaScript 公开的对象和函数。

早期的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的执行的性能,但是这样的架构设计也带来了不少的问题:

  • 生成的机器码会占用大量的内存,这对于大内存的电脑还好说,但对于早期的安卓低内存的设备,基本是不能承受的,并且有些代码仅仅执行一次,没有必要直接生成机器码
  • 缺少中间层机器码,无法实现一些优化策略,导致V8引擎性能提升缓慢
  • 之前的编译器无法很好的支持和优化JS的新语法特性

因此,V8团队为了解决这些问题,用了三年半时间开发了一套新的V8架构,V8团队称之为设计顶峰的新架构。

新的V8架构是怎样的?

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

image.png

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

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

image.png

所以整体来说,就是处于一个运行字节码和优化的机器代码共存的一个状态,随着JS源码不断的被执行,会有更多的源码被标记为热点代码,就会产生更多的机器代码。

逆向还原(deoptimization)

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

例如:有一个sum函数,在函数声明时,JS引擎并不知道参数x,y是什么类型,但在后面多次调用中,传入的x,y都是整型,sum函数被识别为热点函数,解释器将收集到的类型信息和该函数对应的字节码发送给编译器,于是编译器生成的优化后的机器代码中就假定了sum函数的参数x,y都是整型,之后遇到该函数的调用就直接使用运行更快的机器代码,如果此时调用sum函数传入了字符串,机器代码不知道如何处理字符串的参数,于是就需要进行deoptimization回退字节码,由解释器来解释执行。

因此我们尽量不要把一个变量的类型变来变去,对传入函数的参数的类型也是最好保持固定,否则会给V8引擎损失一定的性能

image.png

新V8架构的好处

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

image.png

参考资料

8分钟带你了解V8引擎是如何运行JS

V8官网

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改