吃掉V8,看V8引擎如何翻江倒海

一直好奇V8引擎的运转原理和垃圾回收,是如何提升浏览器的渲染,今天,我们就一起来一探究竟。

在了解V8引擎以前,我们先来了解一下什么是javascript引擎?

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

那,什么是V8引擎?

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

了解V8后,我们一起来看一下V8的作用原理

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

接下来,我们一起来看一下V8编译运行过程

首先我们了解一下在执行编译运行过程中所用到的几个类

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

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

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

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

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

    FullCodeGenerator:AstVisitor类的子类,通过遍历抽象语法树来为JavaScrit生成本地代码

进行编译运行过程,让我们一起看看V8是如果运转的

一、解析器parser将js代码转化为抽象语法树

二、解释器Interpreter负责将抽象语法树解释称字节码byteCode,同时解释器也有直接解释执行byteCode的能力

三、编译器负责编译解析出运行更加高效的机器代码(此处需要注意V5.9版本前没有解释器,是由两个编译器组成,参考下图,观看两个版本的编译过程

V5.9后

V5.9前

第一步,经过解析器parse解析为抽象语法树后,经过Full-codegen编译器生成浏览器可是别的机器代码,而不进行中间转换,Full-codegen编译器生成的代码是未被优化的代码。

当代码运行一段时间后,V8引擎中的分析器线程收集到了足够多的数据来帮助另一个编译器crankShaft来做代码优化,需要优化的源码需要重新生成新的ast抽象语法树,新的ast再经过crankShaft编译生成优化后的机器代码,来提升运行效率。具体过程如下图所示

老版本的编译原理主要存在以下缺点?

1、机器码占用大量内存

2、缺少中间机器码,无法实现一些优化策略

3、不能很好的支持和优化新的js语法特征

于是v8团队为解决以上问题,开发了一套新的V8架构,接下来,我们一起走进V8新世界吧~~~

一,parse阶段没有变化,在生成AST阶段后,新增了基准解释器这一流程igniton,来生成字节码,此时AST就被清除掉了,释放内存空间。生成的byteCode直接被解释器执行,同时生成的byteCode将作为基准执行模型,字节码更加简洁,生成的byteCode大小相当于等效的基准机器代码的25% ~ 50%左右。

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

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

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

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

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

以上就是V8编译过程,感兴趣的小伙伴,欢迎提出批评意见,还是跟以前一样,我们将会留一个小问题,留待下一篇文章的时候给出解答,今天的问题是,V8引擎在处理js执行过程中,都有哪些优化策略?还是老规矩,下篇文章会给出参考答案,也欢迎小伙伴积极留言回答。

你可能感兴趣的:(吃掉V8,看V8引擎如何翻江倒海)