原文链接 How JavaScript works: an overview of the engine, the runtime, and the call stack by Alexander Zlatkovjavascript
几周以前咱们开始了一系列的文件旨在深刻了解JavaScript和它是如何运行的:咱们认为,经过了解JavaScript的组成部分以及它们如何一块儿发挥做用,你可以编写出更好的代码和应用。html
这一系列的第一部提供了引擎,运行时和调用栈的概览。第二篇将深刻了解V8引擎。咱们还将提供一些有关如何编写更好的JavaScript代码的快速提示,这也是咱们SessionStack(做者所在公司)开发团队在构建产品时遵循的最佳作法。java
JavaScript引擎是执行JavaScript代码的程序或解释器。 JavaScript引擎能够实现为标准解释器,也能够做为即时编译器将JavaScript编译为某种形式的字节码。git
如下列表是实现了JavaScript引擎的流行项目github
V8引擎是由Google创建的开源工程,使用C++编写。该引擎在Chrome内部使用。和其余引擎不同,V8也被用于流行的Node.js中。编程
在V8的5.9版本出来以前,引擎使用两种编译器(后续版本中已经使用TurboFan 替代了前二者, 这里终于原文进行翻译,最新的编译器你们请点前面的链接来学习):数组
V8引擎在内部使用了多线程:浏览器
当首次执行JavaScript代码的时候,V8的full-codegen直接原模原样的转义解析了的JavaScript代码为机器码。这使得它能快速的执行机器码。请注意,V8不会以这种方式使用中间字节码表示,从而无需解释器。缓存
当你的代码执行了一段时间,探查器线程聚集了足够的数据肯定那个方法须要被优化安全
下一步,Grankshaft 开始在另一个线程进行优化。它将JavaScript抽象语法树转换为称为Hydrogen的高级静态单分配(SSA)表示形式,并尝试优化该Hydrogen图。大多数优化都在此级别上完成。
第一个优化是尽量的提早内联尽量多的代码。内联是将调用部分(调用函数的代码行)替换为被调用函数的主体的过程。这个简单的步骤可以使后续优化变得更有意义。
JavaScript是基于原型的语言:没有使用克隆建立的类和对象。 JavaScript仍是一种动态编程语言,这意味着能够在实例化对象后轻松地添加或删除属性。
大多数JavaScript解释器使用类字典的结构(基于哈希函数)将对象属性值的位置存储在内存中。与非动态编程语言(例如Java或C#)相比,在JavaScript使用这种结构检索属性的值在计算上更加昂贵。在Java中,全部对象属性都是在编译以前由固定的对象结构肯定的,而且没法在运行时动态添加或删除(C#具备动态类型,这是另外一个主题)。结果就是,属性的值(或指向这些属性的指针)能够做为连续缓冲区存储在内存中,而且在每一个缓冲区之间具备固定偏移量。能够根据属性类型轻松肯定偏移量的长度,可是在JavaScript中这是不可能的,由于JavaScript在运行时能够更改属性类型。
因为使用字典查找对象属性在内存中的位置效率很低,所以V8改用了另外一种方法:隐藏类。隐藏类的工做方式相似于Java之类的语言中使用的固定的对象结构(类),但它们是在运行时建立的。如今,让咱们看看它们的实际外观:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
复制代码
一旦"new Point(1,2)" 执行了,V8将会建立一个隐藏的类称之为“C0”
C0类上尚未任何属性,因此"C0"是空的。
第一条语句“this.x = x”执行(在Point 函数内部),V8将会基于“ C0”建立第二个隐藏类“C1”,“C1”描述了能够找到X属性的内存位置(至关于对象指针)。在这个例子中,“x”能够存储的偏移量是0,也就是说能够把在内存中的Point对象看为一个连续的缓冲区,第一个偏移值就与“x”相对应。若是属性"x"添加到对象"point"中,V8也会经过“类转换(class transition)”更新"C0",将隐藏类从“C0”转换到“C1”。如今隐藏类变成了“C1”。
当语句“this.y = y” (在Point 函数内部,this.x=x后面)被执行的时候,上诉步骤会重复执行。
新的隐藏类“C2”被建立,“类转换”将被应用于“C1”,此时属性“y”被加入Point对象中(该对象已经包含了属性x)。隐藏类从“C1“转换到“C2”。
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
p1.a = 5;
p1.b = 6;
var p2 = new Point(3, 4);
p2.b = 7;
p2.a = 8;
复制代码
如今,你假设p1和p2会应用相同的隐藏类和类转换。可是,这是不正确的,对于p1,首先添加的是a属性,而后添加的b属性,可是对于p2首先添加的是b属性而后添加的是a属性。所以,p1和p2是不一样的隐藏类和不一样的类转换。在这种状况下,最好以相同的顺序初始化动态属性,以便复用隐藏类。
V8充分利用另外一种称为“内联缓存”的优化动态语言的技术。内联缓存依赖于这种情形:对相同方法的调用每每发生在相同类型的对象上。
内联缓存的深刻解释请参考这里。
咱们将介绍内联缓存的通常概念(以防你没有时间了解上述的深刻说明)
它是如何工做的?V8维护了在最近的方法调用中做为参数传递的对象类型的缓存。使用此信息能够对未来做为参数传递的对象类型作出假设。若是V8可以很好地假设将来将传递给方法的对象的类型,则它能够绕开找出如何访问对象属性的过程,而可使用先前查找到的对象的隐藏类的存储信息(该信息能够经过偏移量访问属性)。
隐藏类和内联缓存是如何相关联的?当在某个对象上调用方法的时候,V8引擎会查询对象的隐藏类去决定是否使用偏移量访问对象的属性。若是两次调用了一样的方法到一样的隐藏类。在对相同的隐藏类成功调用同一方法两次以后,V8会省略了隐藏类查找,只是将属性的偏移量添加到对象指针自己。对于之后使用该方法的全部调用,V8引擎都假定隐藏类未更改,并使用之前查找中存储的偏移量直接跳转到特定属性的内存地址。这大大提升了执行速度。
举例(注:由于做者这一段说的比较模糊,因此参考了其余文章添加了一个例子,原文中并无该示例):
function getX(o) {
return o.x;
}
复制代码
若是是JSC(JavaScriptCore和V8同样是JS引擎,见上文),会生成一下字节码。
JSC还将内联缓存嵌入到get_by_id指令中,该指令由两个未初始化的插槽组成。其中Shape和上文提到的内联类同样,只是表达方式不同,它属于SpiderMonkey。
如今假设咱们使用对象{x:'a'}调用getX。该对象的Shape具备属性“x”,而且Shape存储该属性x的偏移量和属性。首次执行该函数时,get_by_id指令查找属性“x”并发现该值存储在偏移量0处。
内联缓存也是为何同类型的对象共享隐藏类如此重要的缘由。若是你建立了两个相同类型的对象可是却有不一样的隐藏类(咱们以前提到过的)。即便两个类具备相同的类型,V8将不会使用内联缓存,由于他们的隐藏类为同一个属性分配了不一样的偏移量。
一旦Hydrogen图被优化,Crankshaft将其下降为较低级别的表示形式称为Lithium。大多数的Lithium的实现是特定结构。寄存器分配也发生在此级别。
最终,Lithium编译为机器码。 而后称之为OSR的开始执行堆叠替换(on-stack replacement)。在咱们编译和优化明显长的方法的时候,会先运行它。V8不会忘记重启优化后的代码的时候执行会变慢。因此,它会转换咱们全部的上下文(堆,寄存器),以便咱们能够在执行过程当中切换到优化版本。这是一个很是复杂的任务,请记住,在全部的优化中,V8已经在最开始内联了代码。V8不是惟一可以作到这一点的引擎。
有一种称为反优化的保护措施,能够进行相反的转换,并在假设引擎再也不适用的状况下还原为未优化的代码。
对于垃圾收集,V8使用标记清除的传统世代方法来清理旧一代的对象。标记阶段应该中止JavaScript执行。为了控制GC成本并使执行更加稳定,V8使用了增量标记:不是遍历整个堆而是尝试标记每一个可能的对象,而是遍历堆的一部分,而后恢复正常执行。下一个GC将从上一个堆遍历中止的位置继续。这容许在正常执行期间很是短的暂停。如前所述,清除阶段由单独的线程处理。
随着2017年初V8 5.9的发布,引入了新的执行管道。这个新的管道在实际的JavaScript应用程序中实现了更大的性能改进并显著节省了内存。
新的执行管道基于Ignition,V8解释器,TurboFan,V8最新优化的编译器。
你能够从V8团队的文章中查看。
自从V8 5.9发布以来full-codegen和Crankshaft (从2010年开发服务v8的技术)不在被v8用于Javascript的执行。 v8团队一直在努力跟上新的JavaScript语言功能以及这些功能所需的优化。
这意味着整个V8未来将具备更加简单和可维护的体系结构。
这些提升仅仅是开始, 新的Ignition和TurboFan管道为进一步优化铺平了道路,这些优化将在将来几年内提升JavaScript性能并缩小V8在Chrome和Node.js中的占用空间。
最后,这是有关如何编写通过优化的JavaScript的一些技巧。你能够从上文中轻松获得这些内容,可是,为方便起见,如下是摘要: