V8引擎对JS带来的改变

405次阅读  |  发布于4年以前

两类型语言

一、编译型语言:在程序执行之前必须进行专门的编译过程,如C、C++、Java等。

编译型语言有以下特点:

二、解释型语言:支持动态类型,弱类型,在程序运行的时候才进行编译,而编译前需要确定变量的类型,效率比较低,对不同系统平台有较大的兼容性。

解释型语言有以下特点:

三、比较:

随着web相关技术的发展,JavaScript所要承担的工作也越来越多了,早就超越了“表单验证”的范畴,这就

更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一引擎而生,在node中也是采用该引擎来解析JavaScript。

V8引擎

V8引擎使用C++开发,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释行,V8将其编译成原生机器码,并且使用了如内联缓存等方法来提高性能。有了这些功能以后,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、Linux、android等,也支持其他硬件架构,如ARM,X64等。具有很好的可移植性和跨平台特性。

数据表示

JavaScript是一种动态类型语言,在编译时并不能准确知道变量的类型,只可以在运行时确定,这就不像C++或者Java等静态类型语言,在编译时就可以确切的知道变量的类型。在运行时计算和决定变量的类型,会严重影响语言性能,这也就是JavaScript运行效率比C++或是Java低很多的原因之一。

在C++中,源代码需要经过编译才能执行,在生成本地代码的过程中,变量的地址和类型就已经确定,运行本地代码时利用数组和位移就可以存取变量和方法的地址,不需要再进行额外的查找,几个机器指令即可完成,节省了确定类型和地址的时间。JS是无类型语言,无法在执行时就知道变量的类型和地址,所以需要确定。

JS和C++的几个区别:

在代码执行过程中,变量的存取是非常普遍和频繁的,通过偏移量来存取,使用少数汇编指令就能完成,如果通过属性名匹配则需要更多的汇编指令,也需要更多的内存空间。

在JS中,除了boolean,number,string,null,undefined五种基本类型,其他的数据都是对象,V8使用一种特殊的方式来表示他们,进而优化JS的内部表达问题。

在V8中,数据的内部表示由数据的实际内容和数据的句柄构成。数据的实际内容是变长的,类型也是不同的;句柄大小固定,包含指向数据的指针。这种设计可以方便V8进行垃圾回收和移动数据内容,相比于直接使用指针,使用者使用句柄,只需要修改句柄中的指针,而指针的修改对使用者是透明的。

除少数数据(如整型数据)由句柄本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度。

JavaScript对象在V8中的实现包含三部分:隐藏类指针,V8为JS对象创建的隐藏类;属性值指针,指向该对象的属性值;元素值指针,指向该对象的属性。

工作过程

在V8引擎中,JavaScript相关代码并非是一下完成编译的,而是在某些代码需要执行时才会进行编译,这就提高了响应时间,减少了时间开销。源代码先被解析成抽象语法树(AST),然后使用解释器或者编译器转换为Bytecode或者Machine code这种本地可执行代码。

编译阶段

JavaScript代码的编译过程:

1、Script类调用Compiler类的Compile函数为其生成本地代码;

2、Compile函数先试用Parser类生成AST,在使用FullCodeGenerator类生成本地代码;

3、本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编语言。

4、由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间 优化也就无从谈起。

在执行编译之前,V8会构建众多的全局对象并加载一些内置的库来构建一个运行环境。而且在JavaScript源代码中,并非所有的函数都被编译成本地代码,而是延迟编译,在调用时才会编译。

运行阶段

为了性能提升,V8在生成本地代码后,使用数据分析器(profiler)采集一些信息,然后根据这些数据将本地代码进行优化,生成更高效的本地代码,这是一个逐步改进的过程。当发现优化后的代码还不如未优化的代码,V8会退回到原来的代码,也就是优化回滚。

运行阶段过程描述:

1、先根据需要编译和生成这些本地代码;

2、在V8中,函数是一个基本单位,当某个JS函数被调用时,V8会查找该函数是否已生成本地代码,如果已经生成,则直接调用该函数。否则,就生成该函数的本地代码。

3、执行编译后的代码为JavaScript构建JS对象,这需要Runtime类来辅助创建对象,并需要从Heap类分配内存。

4、借助Runtime类中的辅助函数来完成一些功能,如属性访问等。最后,将不用的空间进行标记清除和垃圾回收。

优化回滚

V8中有一个Ignition字节码编辑器,TurBoFan和Ignition结合起来共同完成JavaScript的编译,消除了CranShaft这个旧的编辑器,并让新的Ignition直接从字节码来优化代码,并当需要反优化的时候就直接反优化到字节码,而不需要考虑到JS源码。

隐藏类

V8借用了类和偏移位置的思想,将本来通过属性名匹配来访问属性值的方法进行了改进,使用类似C++编译器的偏移位置机制来实现,这就是隐藏类。

隐藏类将对象划分成不同的组,对于组内对象拥有相同的属性名和属性值的情况,将这些组的属性名和对应的偏移位置保存在一个隐藏类中,组内所有对象共享该信息,同时也可以识别属性不同的对象。

内嵌缓存

正常访问对象属性的过程:首先获取隐藏类的地址,然后根据属性名查找偏移值,然后计算该属性的地址。如果将之前查询的结果缓存起来,可以供再次访问,这就是内嵌缓存。

内嵌内存的思路是,将初次查找的隐藏类和偏移值保存起来,当下次查找的时候,先比较当前对象是否为之前的隐藏类,如果是直接使用之前的缓存结果,减少再次查表的时间。但是如果一个对象有多个属性,缓存失误的概率就会提高,因为属性的类型变化后,对象的隐藏类也会变化,与之前的缓存不一致,需要重新使用之前的方法查找哈希表。

快照

V8引入了快照机制,将内置的对象和函数加载之后的内存保存并序列化。序列化以后的结果很容易反序列化,经过快照机制的启动时间可以缩减几毫秒。快照机制也可以将一些开发者认为需要的JS文件序列化来减少处理事件。

总结

随着V8引擎的发展,我们可以在编程中注意一些问题来做到性能优化:

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8