内联缓存
V8利用另一种技术来优化动态类型语言,称为内联缓存。内联缓存依赖于观察到对相同方法的重复调用往往发生在相同类型的对象上。可以在此处找到对内联缓存的深入解释。
我们将讨论内联缓存的一般概念(如果您没有时间进行上面的深入解释)。
那么它是怎样工作的?V8维护一个在最近的方法调用中作为参数传递的对象类型的缓存,并使用此信息来假设将来作为参数传递的对象类型。如果V8能够对将传递给方法的对象类型做出很好的假设,它可以绕过确定如何访问对象属性的过程,而是使用先前查找到对象的存储信息。隐藏的类。
那么隐藏类和内联缓存的概念是如何相关的呢?每当在特定对象上调用方法时,V8引擎必须执行对该对象的隐藏类的查找,以确定访问特定属性的偏移量。在将同一方法成功调用两次到同一个隐藏类之后,V8省略了隐藏类查找,只是将属性的偏移量添加到对象指针本身。对于该方法的所有未来调用,V8引擎假定隐藏类未更改,并使用先前查找中存储的偏移直接跳转到特定属性的内存地址。这大大提高了执行速度。
内联缓存也是为什么相同类型的对象共享隐藏类非常重要的原因。如果你创建两个相同类型和不同隐藏类的对象(正如我们之前的例子中所做的那样),V8将无法使用内联缓存,因为即使这两个对象属于同一类型,它们对应的隐藏类也是如此为其属性分配不同的偏移量。
这两个对象基本相同,但“a”和“b”属性是按不同顺序创建的。
编译到机器代码
氢气曲线图优化后,Crankshaft将其降低到称为锂的低级别表示。大多数Lithium实现都是特定于体系结构的。寄存器分配发生在此级别。
最后,Lithium被编译成机器代码。然后发生了一些叫做OSR的事情:堆栈替换。在我们开始编译和优化一个明显长期运行的方法之前,我们可能正在运行它。V8不会忘记它只是慢慢执行以重新启动优化版本。相反,它将转换我们拥有的所有上下文(堆栈,寄存器),以便我们可以在执行过程中切换到优化版本。这是一项非常复杂的任务,请记住,在其他优化中,V8最初已经内联了代码。V8并不是唯一能够做到这一点的引擎。
有一种称为去优化的保护措施可以进行相反的转换,并在发动机制造的假设不再适用的情况下恢复到非优化代码。
垃圾收集
对于垃圾收集,V8采用传统的标记和扫描方式来清理旧一代。标记阶段应该停止JavaScript执行。为了控制GC成本并使执行更稳定,V8使用增量标记:不是遍历整个堆,尝试标记每个可能的对象,它只是遍历堆的一部分,然后恢复正常执行。下一个GC停止将从上一个堆行走停止的位置继续。这允许在正常执行期间非常短暂的暂停。如前所述,扫描阶段由单独的线程处理。
点火和TurboFan
随着2017年早些时候发布V8 5.9,引入了新的执行管道。这个新的管道在实际的 JavaScript应用程序中实现了更大的性能提升和显着的内存节省。
新的执行管道建立在Ignition,V8的解释器和TurboFan(V8的最新优化编译器)之上。
您可以在这里查看 V8团队关于该主题的博客文章。
自从V8的5.9版本问世以来,V8已经不再使用全代码和Crankshaft(自2010年以来为V8提供服务的技术)用于JavaScript执行,因为V8团队一直在努力跟上新的JavaScript语言功能并且这些功能需要优化。
这意味着整体V8将具有更简单,更易维护的架构。
Web和Node.js基准测试的改进
这些改进只是一个开始。新的Ignition和TurboFan管道为进一步优化铺平了道路,这些优化将在未来几年内提升JavaScript性能并缩小V8在Chrome和Node.js中的占用空间。
最后,这里有一些关于如何编写优化良好的JavaScript的技巧和窍门。您可以从上面的内容轻松地推导出这些内容,但是,这里有一个方便的摘要: