V8 引擎是如何工做的?

V8 引擎是如何工做的?

本文翻译自:How the V8 engine works?javascript

​ V8是谷歌德国开发中心构建的一个JavaScript引擎。它是由C++编写的开源项目,同时被客户端(谷歌浏览器)和服务器端(Node.js)应用使用。java

​ V8最初是为了提升web浏览器中的JavaScript运行性能设计的。为了提高性能,V8将JavaScript代码翻译为更高效的机器语言,而不是使用解释程序。它经过实现一个JIT(Just-In-Time,即时)编译器来将JavaScript代码编译为机器语言,就像不少现代JavaScript引擎如SpiderMonkey或Rhino(Mozilla)作的那样。V8和它们主要的区别是它不会生成字节码或其余中间代码。git

​ 本篇文章主要目的是展现和理解V8是如何为了生成优化代码工做的(为了客户端或服务器端应用)。若是你有过"我应该在意JavaScript的性能么?"这样的疑惑,我会引用Daniel Clifford (V8团队的研发组长与经理)的这句话来回答你:github

它不只仅是为了让你如今的应用运行得更快,它是为了将不可能变为可能。web

v8引擎

Hidden class

​ JavaScript是一个基于原型的语言:在使用克隆进程的时候,并不会产生类和对象。JavaScript仍是动态类型的:类型和类型信息并不明确,对象的属性也能够动态的添加或删除。如何高效地访问类型和属性是V8的第一个大的挑战。并不像大多数JavaScript引擎作的那样使用类字典数据结构存储对象属性并动态查找属性位置,V8在运行时建立hidden classes(隐藏类)来生成一个内部的类型系统表示和提升属性访问速度。数组

举个例子,咱们建立一个Point构造函数和两个Point对象:浏览器

hidden class

若是布局相同,以这个例子为例,pq 属于相同的V8建立的hidden class。这还显示出了使用hidden classes的另外一个优势:它让V8能够将属性相同的对象划为一组。在这里 pq 使用相同的优化代码缓存

如今假设咱们想要在声明以后再给 q 对象添加一个 z 属性(这对动态类型语言来讲很正常)。服务器

V8会如何处理这种状况?实际上,V8在每一次构建函数声明新的属性是都会建立一个新的hidden class,并持续跟踪hidden class 的变化。为何?由于若是建立了两个对象(pq),在建立后第二个对象q又被添加了一个属性,V8须要在保持上一个建立的hidden class(为第一个对象 p 建立) 的同时,为新的动态添加的属性建立一个新的hidden class(为第二个对象 q 建立)。数据结构

transition

每次建立一个新的hidden class时,前一个hidden class都进行一次类转换更新,指示要使用哪一个hidden class而不是它。

代码优化

由于V8为每个属性建立一个新的hidden class,hidden class的建立应该尽可能少。所以,咱们须要尽可能避免在对象建立后添加属性,同时以相同的顺序初始化对象成员(来减小hidden classes的不一样树的建立)。

单态操做(Monomorphic operations)指只在对象上的使用相同hidden class的操做。V8在咱们调用函数时会新建一个hidden class。若是咱们使用不一样的参数类型再次调用此函数,V8须要建立另外一个hidden class:所以尽可能编写单态代码而不是多态代码

V8优化JavaScript代码的更多例子

切换值

为了更高效地描述数和JavaScript对象,在V8中,二者均使用32位值表示。其中1位表示这个值是对象(flag = 1)仍是数(flag = 0)。若是一个数比31位大,V8会把它转换为double存储在新建的一个对象中。

代码优化:若是可能的话,尽可能使用31位有符号数,来减小上述的高代价的操做。

数组

V8使用两种不一样的方法来操做数组:

  • 快速元素(Fast elements):为无间隙的密集数组设计。它们使用线性存储缓存,使得访问很是高效。([1,2,4,5,8])
  • 字典元素(Dictionary elements):为有间隙的稀疏数组设计。使用哈希表缓存,较之快速元素访问代价更高。([1,2,,5,8])

代码优化:尽可能使用 V8 会使用快速元素方法来操做的数组。即减小使用键不是递增数的数组的使用。同时,尽可能避免预分配大数组。在使用中让它本身慢慢增长会更好。同时,不要删除数组中的元素:它会使得数组稀疏。

V8如何编译JavaScript代码?

V8有两个编译器:

  • 一个是"完整"编译器,能够编译任何的JavaScript代码:编译结果为好的代码但不是好的JIT代码。这个编译器的目的就是快速生成代码。为了实现这个目的,它不会进行任何的类型分析,所以它对类型一无所知。相反的,它使用内联缓存(Inline Caches)策略来在程序运行中精炼类型信息。内联缓存很是高效,带来了20倍的速度提高。
  • 另外一个是优化编译器,能够编译大多数JavaScript代码,生成更好的代码。它出现的更晚,并对热函数进行了重编译。优化编译器从内联缓存中获取类型并决定如何对代码进行优化。然而,有些语言特性并无被支持或可能会抛出错误。(应对方法是使用try catch)

代码优化:V8一样支持去优化:优化编译器根据从内联缓存中获取的类型信息进行优化,当此优化后有问题时会去优化。例如,若是生成的hidden class不是指望的那样,V8会抛弃优化代码,返回到完整编译器生成的代码,并从内联缓存中从新获取类型。这个过程很慢,所以应尽可能在函数被优化后不去修改它。

参考资料

  • Google I/O 2012 “Breaking the JavaScript Speed Limit with V8” with Daniel Clifford, tech lead and manager of the V8 team: video and slides.
  • V8: an open source JavaScript engine: video of Lars Bak, V8 core engineer.
  • Nikkei Electronics Asia blog post: Why Is the New Google V8 Engine So Fast?
相关文章
相关标签/搜索