V8引擎是如何工做?

V8是google开发的JavaScript引擎, 它是开源的 ,并且是用C++编写的。它是用于客户端(Google Chrome)和服务器端(node.js)JavaScript应用程序。java

V8最初旨在提升Web浏览器中JavaScript执行的性能。为了提高速度,V8将JavaScript代码转换为更高效的机器语言,而不是使用解释器。它经过实现 JIT(即时编译器)将JavaScript代码编译成机器代码,就像许多现代JavaScript引擎(如SpiderMonkey或Rhino(Mozilla))所作的那样。与V8的主要区别在于它不会产生字节码或任何中间代码。node

本文的目的是展现和理解 V8如何工做,以便为客户端或服务器端应用程序生成优化的代码。若是您已经在问本身“我应该关心JavaScript性能吗?”那么我将回答Daniel Clifford(技术主管和V8团队经理)的一句话:“这不只仅是让您当前的应用程序运行得更快,而是关于实现你过去从未作过的事情“。编程

隐藏的class

JavaScript是一种基于原型的语言:no classes,而且使用克隆过程建立对象(原型链)。JavaScript也是动态类型的:类型和类型信息不是显式的,属性能够动态添加到对象中或从中删除。有效访问类型和属性是V8的首要挑战。而不是使用相似字典的数据结构来存储对象属性和进行动态查找来解析属性位置(就像大多数JavaScript引擎同样),V8在运行时建立隐藏类,以便具备内部表示类型系统和改善属性访问时间。数组

让咱们有一个Point函数和两个Point对象的建立:浏览器

https://p1.ssl.qhimg.com/t016...
clipboard.png缓存

若是布局相同(这里是这种状况),则p和q属于由V8建立的相同隐藏类。这突出了使用隐藏类的另外一个优势:它容许V8对属性相同的对象进行分组。这里p和q有必定的代码优化。服务器

如今,让咱们假设咱们想在咱们的q对象以后添加一个z属性,就在它声明以后(对于动态类型语言来讲这是彻底没问题的)。数据结构

V8将如何处理这种状况?事实上,每当构造函数声明一个属性并跟踪隐藏类的变化时,V8 就会建立一个新的隐藏类。为何?由于若是建立了两个对象(p和q)而且在建立后将成员添加到第二个对象(q),则V8须要保留最后建立的隐藏类(对于第一个对象p)并建立一个新对象(对于第二个对象q)与新成员。app

https://p4.ssl.qhimg.com/t01c...
clipboard.png编程语言

每次建立一个新的隐藏类时,前一个隐藏类都会更新一个类转换,指示必须使用哪一个隐藏类。

所以:

  • 初始化构造函数中的全部对象成员(所以实例稍后不会更改类型)
  • 始终以相同的顺序初始化对象成员

代码优化

由于V8为每一个属性建立一个新的隐藏类,因此应该将隐藏的类建立保持在最低限度。为此,请尽可能避免在建立对象后添加属性,并始终以相同的顺序初始化对象成员(以免建立不一样的隐藏类树)。

[Update ]另外一个技巧:单态操做是仅对具备相同隐藏类的对象起做用的操做。当咱们调用一个函数时,V8会建立一个隐藏类。若是咱们用不一样的参数类型再次调用它,V8须要建立另外一个隐藏类:首选单态代码到多态代码

有关V8如何优化JavaScript代码的更多示例

标记值

为了有效地表示数字和JavaScript对象,V8表示具备 32位值。它使用一个位来知道它是一个对象(flag = 1)仍是一个整数(flag = 0),这里称为SMall Integer或 SMI ,由于它的31位。而后,若是数值大于31位,则V8将对该数字进行选择,将其变为双精度并建立一个新对象以将数字放入其中。

代码优化:尽量使用31位带符号数字,以免对JavaScript对象进行消耗性能的封装操做。

数组

V8使用两种不一样的方法来处理数组:

  • 快速元素:专为那些键组很是紧凑的阵列而设计。它们具备线性存储缓冲区,能够很是有效地访问它。
  • 字典元素:专为稀疏数组而设计,它们内部没有全部元素。它其实是一个哈希表,它的性能消耗比“快速元素”更昂贵。

代码优化:确保V8使用“快速元素”来处理数组,换句话说,避免使用稀疏数组。另外,尽可能避免预先分配大型数组。最后,不要删除数组中的元素:它使键集稀疏。

a = new Array();
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // Oh no!
}
//vs.
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // Much better! 2x faster.
}

此外,双精度阵列更快 - 数组的隐藏类跟踪元素类型,而且仅包含双精度的数组是未装箱的(这会致使隐藏的类更改)。可是,因为装箱和拆箱,粗心操做阵列会致使额外的工做 - 例如

var a = new Array();
a[0] = 77;   // Allocates
a[1] = 88;
a[2] = 0.5;   // Allocates, converts
a[3] = true; // Allocates, converts

效率低于:

var a = [77, 88, 0.5, true];

V8如何编译JavaScript代码?

V8有两个编译器!

一个“完整”编译器,能够为任何JavaScript生成良好的代码。此编译器的目标是快速生成代码。为了实现其目标,它不进行任何类型分析,也不了解类型。相反,它使用内联缓存或“IC”策略来在程序运行时优化有关类型的知识。IC效率很是高,速度可提升20倍。

优化编译器,可为大多数JavaScript语言生成出色的代码。它稍后会从新编译热门功能。优化编译器从内联缓存中获取类型,并决定如何更好地优化代码。可是,某些语言功能尚不支持,例如try / catch块。(try / catch块的解决方法是在函数中编写“非稳定”代码并在try块中调用函数)

代码优化:V8还支持去优化:优化编译器从内联缓存中对不一样类型作出假设,若是这些假设无效则会进行去优化。例如,若是生成的隐藏类不是预期的类,则V8会抛弃优化的代码并返回到完整编译器以从内联缓存中再次获取类型。此过程很慢,应该经过在优化后尝试不更改功能来避免。

资源

博客评论由Disqus提供

译者注:
关于本文中提到的一些知识点,作一些简单的只是扩展,但愿对大家理解本文有一些帮助;
一、 "JavaScript has no classes"
虽然JavaScript是面向对象的语言,但它不是基于类的语言 - 它是基于原型的语言。
在js和java或其余“基于类”的编程语言中类的工做方式之间存在一些深入的差别。
相关讨论
二、快速元素和字典元素
快速或字典元素:元素的第二个主要区别是它们是快速仍是字典模式。快速元素是简单的VM内部数组,其中属性索引映射到元素存储中的索引。可是,这种简单的表示对于很是大的稀疏/多孔数组而言是至关浪费的,其中只占用不多的条目。在这种状况下,咱们使用基于字典的表示来节省内存,但代价是访问速度稍慢:

const sparseArray = [];
sparseArray[9999] = 'foo'; // Creates an array with dictionary elements.
sparseArray.length
// 10000
sparseArray[0]
// undefined

在这个例子中,分配一个包含10k条目的完整数组会至关浪费。相反,V8会建立一个字典来存储键值描述符三元组。在这种状况下,密钥是'9999',而且使用值'foo'和默认描述符。鉴于咱们没有办法在HiddenClass上存储描述符详细信息,只要您使用自定义描述符定义索引属性,V8就会转向减慢元素:

const array = [];
Object.defineProperty(array, 0, {value: 'fixed' configurable: false});
console.log(array[0]);      // Prints 'fixed'.
array[0] = 'other value';   // Cannot override index 0.
console.log(array[0]);      // Still prints 'fixed'.

引用文档

相关文章
相关标签/搜索