本系列会对v8相关做一下整理,分为以下四部分
-
interpreter/compiler pipeline 包括从源码经过解释和编译的流程,主要是解释器
Ignition,优化编译器TurboFan,无优化编译器Sparkplug,以及中间代码bytecode。 -
object model 包括v8为了加快属性访问进行的优化,主要是
Shape,Inline Cache, andValidityCell。 -
memory structure and gc 包括在v8中的内存结构和垃圾回收算法,主要是垃圾回收过程和
parallel,incrementalandconcurrent相关策略。 -
基于v8的代码优化方法和内存泄漏
js引擎通用工作流程
- 整个流程从我们写的源码开始,解释器将源码解析为AST
- 解析器基于AST生成中间代码bytecode,并解释执行
- 为了更快的执行,编译器将bytecode以及相关profiling data编译为执行更为高效的机器代码。
在具体的bytecode到最终的机器代码过程,不同js引擎会稍有不同,比如SpiderMonkey先后有两个编译器处理。
理解bytecode
bytecode是编译过程中与机器无关的中间代码。如果bytecode的设计和cpu的计算模型一样,则更容易编译成机器码,因此解释器通常是register 或 stack machines,在v8中的解释器Ignition就是一个带累加寄存器的register machine
bytecode 可以通过解析器直接解释执行,但效率较差,具体细节可以参考 DLS Keynote: Ignition: Jump-starting an Interpreter for V8
引入bytecode的原因是bytecode生成的速度比机器码生成的速度快,而且同样的源码处理后占用的内存小。 后面引入一个或多个编译器的原因是机器码的运行速度比bytecode快。
v8中的工作流程
在v8中,解释器是Ignition,在执行bytecode时收集profiling data,当一个函数变hot后(比如因为频繁执行),bytecode和profiling data传递给编译器TurboFan,编译优化为机器代码执行。
以上说的是之前的方案,实际的使用过程中,尽管TurboFan速度很快,但对于js初始执行的性能,编译器没机会进行优化,而解释器Ignition尽管也很快,但其有一些开销无法避免,比如bytecode解码的开销。 因此在当前流程中又引入了一层新的编译器:Sparkplug。
Sparkplug在设计上兼容Ignition的stack frames等(改造较小,但运行快),且没过多优化(编译时间短),将v8在real-world benchmarks的性能提高了5–15%。
Sparkplug在Chrome 91中可用。