V8使用C++开发,并在谷歌浏览器中使用。在运行JavaScript以前,相比其它的JavaScript的引擎转换成字节码(包含执行程序的二进制文件)或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),而且使用了如内联缓存(inline caching)等方法来提升性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。
javascript
2、详解java
1. 字节码(Bytecode):一种包含执行程序、由一序列op代码、数据对组成的二进制文件,是一种中间码,编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为能够直接执行的机器码。c++
原生机器码(machine code):也被称为原生码(Native Code),是电脑的CPU可直接解读的数据,机器码是计算机能够直接执行,而且执行速度最快的代码。编出的程序所有是0和1组成的指令代码浏览器
字节码装换成原生机器码:首先编译器将源码编译成字节码,特定平台上 的虚拟机(如:Java虚拟机)器将字节码转译成能够直接执行的指令。字节码的典型应用为Java bytecode缓存
2. javascript引擎性能优化
语言编译过程:bash
如今其余的js引擎的执行过程大体:源代码 -> 抽象语法树 -> 字节码 -> JIT -> 本地代码闭包
(如:苹果的JavascriptCore引擎,08年引入SquirrelFish,实现了一个字节码寄存器Register Machine;Mozilla公司的SpiderMonkey;微软的Chakra等)ide
抽象语法树函数
V8引擎中没有中间字节码,直接将抽象语法树经过JIT技术转换成本地代码,后经过Profiler采集一些信息,优化本地代码,虽然少了这一阶段的性能优化,但极大减小了转换时间
可是在V8的5.9版本,新增了Ignition字节码解释器,默认启动,缘由是减轻机器码占用的内存空间,提升代码的启动速度,重构V8的代码并下降代码复杂度。
3. V8引擎
3.1 5.9以前的版本:直译成原生机器码,并使用如内联缓存等方法提升性能
3.1.1 数据表示
javascript是一种无类型语言,在编译时不能肯定变量的类型,只能在执行时肯定;而像c++、java等静态类型语言在编译时就能知道变量类型,肯定变量存取地址。在运行时计算和决定类型是会下降运行效率的缘由。
变量的存取在代码执行过程当中是很是广泛和平凡的,js的对象须要经过属性名匹配找到相应的值,须要更多的操做和内存空间。V8使用了一种特殊的方法:数据的内部表示由数据的实际内容和数据的句柄构成。
数据的实际内容长度可变,类型可变;数据的句柄固定大小,包含指向数据的指针。变量存取时经过查找、修改句柄中的指针便可。V8一个句柄对象的大小是4字节(32位设备)和8字节(64位设备),javascriptCore中是8字节。
V8中的指针包含三类:隐藏类指针,是V8为js对象建立的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性。
3.1.2工做过程
V8中,js代码是在须要执行时才进行编译,而不是一次性所有编译;这也就提升了响应时间。源代码先被解析器转成抽象语法树(AST),而后使用JIT编译器的全代码生成器将其直接生成本地客执行代码。为了提高性能,V8在生成本地代码后,使用数据分析器(profiler)采集一些信息,而后根据这些信息优化本地代码,若是优化后代码性能更差,就会优化回滚。
在执行编译前,V8会构建众多全局对象,并加载一些内置的库(如math库),来构建运行环境。
3.1.3优化回滚
V8没有通过中间表示层的优化,即先编译肯定变量类型等,因此引入Crankshaft编译器对热点函数,基于javascript源代码,进行优化分析。Crankshaft为了性能考虑,默认代码稳定且变量类型不变,生成高效的本地代码;但若遇到变量类型在执行过程当中改变的状况,V8会将该编译器作的优化进行回滚,即从新从源码开始再次编译。
var counter =
0;
function
test(x, y) {
counter++;
if (counter < 1000000)
{
// do something
return 'jeri';
}
var unknown = new Date();
console.log(unknown);
}复制代码
在未执行到new Date()以前,并不肯定unknown变量的类型,执行到时,V8只能将这部分代码进行回滚。优化回滚是很耗时的操做。
3.1.4隐藏类
使用point构造了两个对象p和q,这两个对象有相同的属性名,V8将他们归为同一隐藏类,具备相同的偏移位置信息,p和q共享这一信息,进行属性访问时,只需根据隐藏类的偏移信息便可。但假如,代码执行后,对象q执行了q.z = 5,则p和q不在具备相同的隐藏类,q是一个新的隐藏类。
隐藏类转换取决于将属性添加到对象的顺序:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p = new Point(1, 2);
p.a = 5;
p.b = 6;
var q = new Point(3, 4);
q.b = 7;
q.a = 8;复制代码
而在该函数中,p和q添加属性的顺序不一样,隐藏类的偏移量不一样,也是两个不一样的隐藏类。
3.1.5内联缓存
正常访问对象的过程:首先获取隐藏类的地址,而后根据属性名查找偏移量,而后计算该属性的地址。屡次变量存取,就要重复执行这一过程,也较耗时。所以,V8提供了内嵌缓存,即将初次查找的隐藏类和偏移量保存起来,当下次查找相同对象时,能够省略计算地址的过程。可是若是一个对象有多个属性,缓存失误的几率就会提升,由于某个属性的类型变化后,对象的隐藏类也会发生变化,就与以前的缓存不一致,须要从新计算。
3.1.6内存管理
V8垃圾回收机制限制js使用的内存(若是可以使用内存太大,垃圾回收时须要耗费更多的资源和时间),所以对内存进行管理:分配和回收。
内存的管理组要由分配和回收两个部分构成。V8的内存划分以下:
Zone:管理小块内存。其先本身申请一块内存,而后管理和分配一些小内存,当一块小内存被分配以后,不能被Zone回收,只能一次性回收Zone分配的全部小内存。当一个过程须要不少内存,Zone将须要分配大量的内存,却又不能及时回收,会致使内存不足状况。
堆:管理JavaScript使用的数据、生成的代码、哈希表等。为方便实现垃圾回收,堆被分为三个部分:
年轻分代:为新建立的对象分配内存空间,常常须要进行垃圾回收。为方便年轻分代中的内容回收,可再将年轻分代分为两半,一半用来分配,另外一半在回收时负责将以前还须要保留的对象复制过来。
年老分代:根据须要将年老的对象、指针、代码等数据保存起来,较少地进行垃圾回收。
大对象:为那些须要使用较多内存对象分配内存,固然一样可能包含数据和代码等分配的内存,一个页面只分配一个对象。
3.2 5.9版本
3.2.1引入Ignition字节码解释器的缘由
1).V8对代码的编译是在该段代码被执行时,所以代码须要被解析屡次--绿色代码(总的)先解析一次,当new Person被调用时黄色代码再解析一次,当doWork被调用时红色代码再解析一次。所以,若是闭包嵌套了n层,在Crankshaft预算正确的状况下,代码至少要被V8解析n次。
2).机器码占空间大,若是要将全部js直译的机器码缓存,占用的内存、磁盘空间很大,而退出浏览器再从新打开,执行使用缓存的时间也会很长。如此,Chrome的缓存之做用在js代码的最外层,真正的执行逻辑并无被缓存,这也是致使内存资源被占用被浪费的缘由。
字节码比机器码紧凑更多,能够下降代码内存的占有率
(来源: docs.google.com/presentatio…)
一样的内存分配下,内存占用减小,启动速度加快,使得V8能够指望提早编译全部js代码,并把字节码缓存,二次打开网站时速度就会更快。也就再也不须要Cranshaft这个旧的编译器,引用新的Turbofan直接从字节码优化,并当须要进行反优化的时候,直接反优化到字节码,而不是将机器码反优化到js源码。
(原文出处: docs.google.com/presentatio…)
3、总结
从上述V8引擎的设计特征总结来看,在编码过程当中应注意: