必知概念
深入理解 V8 的工作原理,需要搞清楚的一些概念,下面的解释部分来自百科。
编译器(Compiler)
1、简单讲,编译器就是将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序。
2、一个现代编译器的主要工作流程:源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → 目标代码 (object code) → 链接器 (Linker) → 可执行程序 (executables)
3、高级计算机语言便于人编写,阅读交流,维护。机器语言是计算机能直接解读、运行的。
4、编译器将汇编或高级计算机语言源程序(Source program)作为输入,翻译成目标语言(Target language)机器代码的等价程序。
5、源代码一般为高级语言 (High-level language), 如Pascal、C、C++、Java、汉语编程等或汇编语言,而目标则是机器语言的目标代码(Object code),有时也称作机器代码(Machine code)。
6、对于C#、VB等高级语言而言,此时编译器完成的功能是把源码(SourceCode)编译成通用中间语言(MSIL/CIL)的字节码(ByteCode)。最后运行的时候通过通用语言运行库的转换,编程最终可以被CPU直接计算的机器码(NativeCode)。
解释器(Interpreter)
解释器(英语:Interpreter),又译为直译器,是一种电脑程序,能够把高级编程语言一行一行直接转译运行。解释器不会一次把整个程序转译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去。
抽象语法树(AST)
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
字节码(Bytecode)
字节码(Byte-code)是一种包含执行程序、由一序列 op 代码/数据对组成的二进制文件。字节码是一种中间码,它比机器码更抽象。它经常被看作是包含一个执行程序的二进制文件,更像一个对象模型。字节码被这样叫是因为通常每个 opcode 是一字节长,但是指令码的长度是变化的。每个指令有从 0 到 255(或十六进制的: 00 到FF)的一字节操作码,被参数例如寄存器或内存地址跟随。
即时编译器(JIT)
即时编译器(just-in-time compiler)随虚拟机一起供给的,并可选使用。它把字节码编译成可立即执行的指定平台的可执行代码。其实字节码配合解释器和编译器是最近一段时间很火的技术,比如 Java 和 Python 的虚拟机也都是基于这种技术实现的,我们把这种技术称为即时编译(JIT)。除了 V8 使用了“字节码 +JIT”技术之外,苹果的 SquirrelFish Extreme 和 Mozilla 的 SpiderMonkey 也都使用了该技术。
JIT 的工作过程:
热点代码(HotSpot)
比如一段代码被重复执行多次,这种就称为热点代码。
编译器和解释器
按语言的执行流程,可以把语言划分为:
编译型语言:需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译。比如 C/C++、GO 等都是编译型语言。
解释型语言:每次运行时都需要通过解释器对程序进行动态解释和执行。比如 Python、JavaScript 等都属于解释型语言。
编译型语言执行流程:
解释型语言执行流程:
V8 是如何执行一段 JavaScript 代码的
V8 执行一段代码流程图:解释器 Ignition (点火器),编译器 TurboFan (涡轮增压)
1. 生成抽象语法树(AST)和执行上下文
AST 是如何生成的?
第一阶段是分词(tokenize),又称为词法分析
其作用是将一行行的源码拆解成一个个 token。所谓 token,指的是语法上不可能再分的、最小的单个字符或字符串。你可以参考下图来更好地理解什么 token。
第二阶段是解析(parse),又称为语法分析
其作用是将上一步生成的 token 数据,根据语法规则转为 AST。如果源码符合语法规则,这一步就会顺利完成。但如果源码存在语法错误,这一步就会终止,并抛出一个“语法错误”。
2. 生成字节码
有了 AST 和执行上下文后,解释器 Ignition 会根据 AST 生成字节码,并解释执行字节码。
注意
- 开始 V8 并没有字节码,而是直接将 AST 转换为机器码
- Chrome 运行在的手机上(特别是 512M 内存),内存占用问题:消耗大量的内存来存放转换后的机器码
- 为了解决内存占用问题,引入字节码
字节码和机器码占用空间对比如下图:机器码所占用的空间远远超过了字节码
字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。
3. 执行代码
在 Ignition 执行字节码的过程中,如果发现有热点代码(HotSpot),那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。
拓展
JavaScript 的性能优化
- 提升单次脚本的执行速度,避免 JavaScript 的长任务霸占主线程,这样可以使得页面快速响应交互;
- 避免大的内联脚本,因为在解析 HTML 的过程中,解析和编译也会占用主线程;
- 减少 JavaScript 文件的容量,因为更小的文件会提升下载速度,并且占用更低的内存。