一起稍微了解一下 V8 的工作流程

1,153 阅读4分钟

image.png

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战

Javascript 引擎

简单来说,CPU 并不能直接识别 js 代码,不同的 CPU 只能识别自己对应的指令集,javascript 引擎将 js 代码编译成 CPU 认识的指令集,当然除了编译之外还要负责执行以及内存的管理,js 由引擎直接读取源码,一边编译一边执行,这样效率相对较低,而编译形语言(如 C++ )是把源码直接编译成可直接执行的代码执行效率更高, V8 便在这样的场景下诞生了。

V8 引擎

V8 使用 C++ 开发。在运行 JavaScript 之前,相比其它的 JavaScript 的引擎转换成字节码或解释执行,V8 将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存等方法来提高性能。有了这些功能,JavaScript 程序在 V8 引擎下的运行速度媲美二进制程序。

image.png

V8Javascript 代码编译成抽象语法树再转化成字节码,通过解释器来执行,并通过 JIT 工具将部分字节码转化成可直接执行的本地代码。V8 直接将抽象语法树通过 JIT 技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。虽然少了生成字节码这一阶段的性能优化,但极大减少了转换时间。

image.png

V8编译运行过程

  • Script类:表示是JavaScript代码,既包含源代码,又包含编译之后生成的本地代码,所以它既是编译入口,又是运行入口。

  • Compiler类:编译器类,辅助Script类来编译生成代码,它主要起一个协调者的作用,会调用解释器(Parser)来生成抽象语法树和全代码生成器,来为抽象语法树生成本地代码。

  • Parser类:将源代码解释并构建成抽象语法树,使用AstNode类来创建它们,并使用Zone类来分配内存。

  • AstNode类:抽象语法树节点类,是其他所有节点的基类,它包含非常多的子类,后面会针对不同的子类生成不同的本地代码。

  • AstVisitor类:抽象语法树的访问者类,主要用来遍历抽象语法树。

  • FullCodeGenerator:AstVisitor类的子类,通过遍历抽象语法树来为JavaScrit生成本地代码,进行编译运行过程。

V8是如果运转的

  1. 解析器 Parserjs 代码转化为抽象语法树

image.png

  1. 解释器 Interpreter 负责将抽象语法树解释成字节码 byteCode,同时解释器也有直接执行 byteCode 的能力。AST 此时就被清除掉了,释放内存空间。生成的 byteCode 直接被解释器执行,同时生成的 byteCode 将作为基准执行模型,字节码更加简洁,生成的 byteCode 大小相当于等效的基准机器代码的 25% ~ 50% 左右。

image.png

  1. 编译器负责编译解析出运行更加高效的机器代码。在代码的不断运行的过程中,解释器 interpreter 收集到了很多可以用来优化代码的信息,比如变量的类型,执行函数的次数,这些信息被发送给 V8 编译器。编译器会根据这些信息和字节码来编译出经过优化的新的机器代码。

image.png

  1. 先根据需要编译和生成这些本地代码,也就是使用编译阶段那些类和操作。

  2. V8 中,函数是一个基本单位,当某个 JavaScript 函数被调用时,V8 会查找该函数是否已经生成本地代码,如果已经生成,则直接调用该函数。否则,V8 引擎会生成属于该函数的本地代码。这就节约了时间,减少了处理那些使用不到的代码的时间。

  3. 其次,执行编译后的代码为 JavaScript 构建JS对象,这需要 Runtime 类来辅组创建对象,并需要从 Heap 类(运行本地代码需要使用的内存堆类)分配内存。

  4. 最后,借助 Runtime 类(运行这些本地代码的辅组类,主要提供运行时所需的辅组函数,如:属性访问、类型转换、编译、算术、位操作、比较、正则表达式等)中的辅组函数来完成一些功能,如属性访问等。最后,将不用的空间进行标记清除和垃圾回收(MarkCompactCollector:垃圾回收机制的主要实现类,用来标记、清除和整理等基本的垃圾回收过程, SweeperThread:负责垃圾回收的线程。)