Js工作原理:V8编译器的优化10bet:

来源:http://www.chinese-glasses.com 作者:Web前端 人气:130 发布时间:2020-05-06
摘要:因此,无关的字符,比如空白,注释,变量名长度,函数签名等,不会影响函数的性能。 第一个优化是提前内联尽可能多的代码。内联是用被调用函数的主体替换调用点的过程。这个简

因此,无关的字符,比如空白,注释,变量名长度,函数签名等,不会影响函数的性能。

第一个优化是提前内联尽可能多的代码。内联是用被调用函数的主体替换调用点的过程。这个简单的步骤允许下面的优化更有意义。

解释器的优点、缺点

隐藏类

Tip2: 保持对象属性不变

V8利用了另一种优化动态类型语言的技术,称为内联缓存。内联缓存依赖于这样一种观察,即对同一方法的重复调用往往发生在同一类型的对象上。这里可以找到对内联缓存的深入解释。

Tip7:Try/catch/finally 不是毁灭性的

内联缓存也是为什么相同类型的对象共享隐藏类非常重要的原因。 如果你创建两个相同类型和不同隐藏类的对象,V8将无法使用内联缓存,因为即使这两个对象属于同一类型,它们对应的隐藏类为其属性分配不同的偏移量。

TurboFan的在线JIT风格的编译和优化意味着 V8从源代码到机器代码的转换 结束了。

full-codegen — 一个简单和非常快的编译器,产生简单和相对较慢的机器码。 Crankshaft — 一种更复杂的优化编译器,生成高度优化的代码。

函数根据特定参数位置的值类型更改对象形状。如果此类型发生更改,则函数将去优化并重新优化。

接下来将讨论内联缓存的一般概念。

有两种方法可以进行转换。

JavaScript引擎是执行 JavaScript 代码的程序或解释器。JavaScript引擎可以实现为标准解释器,或者以某种形式将JavaScript编译为字节码的即时编译器。

Tip4:在脚本作用域中声明类

每次将新属性添加到对象时,旧的隐藏类都会更新为指向新隐藏类的转换路径。隐藏类转换非常重要,因为它们允许在以相同方式创建的对象之间共享隐藏类。如果两个对象共享一个隐藏类并且同一属性被添加到它们中,则转换将确保两个对象都接收相同的新隐藏类以及随其附带的所有优化代码。

用类型和范围分析优化即时编译器

V8 — 开源,由 Google 开发,用 C ++ 编写 Rhino — 由 Mozilla 基金会管理,开源,完全用 Java 开发 SpiderMonkey — 是第一个支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用 JavaScriptCore — 开源,以Nitro形式销售,由苹果为Safari开发 KJS — KDE 的引擎,最初由 Harri Porten 为 KDE 项目中的 Konqueror 网页浏览器开发 Chakra  — Internet Explorer Chakra  — Microsoft Edge Nashorn, 作为 OpenJDK 的一部分,由 Oracle Java 语言和工具组编写 JerryScript —  物联网的轻量级引擎

class Point { constructor(x, y) { this.x = x; this.y = y; }}var p1 = new Point(11, 22); // hidden class Point createdvar p2 = new Point(33, 44);p1.z = 55; // another hidden class Point created

内联缓存

下一个tip是V8引擎里的奇淫巧技。

一旦 Hydrogen 图被优化,Crankshaft 将其降低到称为 Lithium 的较低级表示。大部分的 Lithium 实现都是特定于架构的。寄存器分配往往发生在这个级别。

第一种选择是使用解释器。解释器可以有效地逐行翻译和执行。

隐藏类转换取决于将属性添加到对象的顺序。看看下面的代码片段:

更改对象属性的顺序会导致新的隐藏类,因为对象形状中是包含顺序的。

这两个对象基本相同,但是“a”和“b”属性的创建顺序不同。

为了执行任意一段程序,计算机必须将源代码转换成机器可以运行的代码。

随着2017年早些时候发布V8 5.9,引入了新的执行管道。 这个新的管道在实际的JavaScript应用程序中实现了更大的性能提升和显着节省内存。

eval的开销,而不是运行机器代码。

概述

所有这些函数都使用两个隐藏类重新优化。对对象形状的任何修改都是如此。

新的执行流程是建立在 Ignition和 TurboFan之上的。

Try代码块以前容易出现高昂的优化-去优化的周期。如今,当在Try块中调用函数时,turbofan不再显示出显著的性能影响。

为什么要创建V8引擎?

下面是利用V8中的优化来提高性能的7个技巧。前四个重点是减少去优化。

尚未为 Point 定义属性,因此“C0”为空。

然而,这些好处是以缓慢执行为代价的:

自从 V8 5.9 版本问世以来,由于 V8 团队一直努力跟上新的 JavaScript 语言特性以及这些特性所需要的优化,V8 团队已经不再使用 full-codegen 和 Crankshaft。

在节点海中,节点表示计算,变表示依赖关系。

最后,Lithium 被编译成机器码。然后就是 OSR :on-stack replacement。在我们开始编译和优化一个明确的长期运行的方法之前,我们可能会运行堆栈替换。 V8 不只是缓慢执行堆栈替换,并再次开始优化。相反,它会转换我们拥有的所有上下文,以便在执行过程中切换到优化版本上。这是一个非常复杂的任务,考虑到除了其他优化之外,V8 最初还将代码内联。 V8 不是唯一能够做到的引擎。

通过对V8引擎框架的理解,我们还可以推断出上面没有列出的其他优化方法,并尽可能重用方法来利用内联。现在您已经了解了JavaScript编译及其对日常JavaScript使用的影响。

大多数 JavaScript 解释器使用类似字典的结构来存储对象属性值在内存中的位置,这种结构使得在 JavaScript 中检索属性的值比在 Java 或 C# 等非动态编程语言中的计算成本更高。

将程序表示为JavaScript操作符。将程序表示为中间运算符。将程序表示为机器操作符。使用顺序约束安排执行顺序。创建一个传统的控制流图。

编译成机器码

在2008年发布时,V8引擎最初直接将源代码编译为机器代码,跳过了中间字节码表示。在发布时,V8就比竞争对手快了10倍。

那么它是如何工作的呢? V8 维护了在最近的方法调用中作为参数传递的对象类型的缓存,并使用这些信息预测将来作为参数传递的对象类型。如果 V8 能够很好地预测传递给方法的对象的类型,它就可以绕过如何访问对象属性的过程,而是使用从以前的查找到对象的隐藏类的存储信息。

Crankshaft过去是使用一个函数的字节数来决定是否内联一个函数的。而TurboFan是建立在AST上的,他使用AST节点的数量来决定函数的大小。

如何编写优化的 JavaScript

我们将讨论这些优化方法以及代码如何利用这些优化。通过了解JavaScript执行的基础知识,您不仅可以理解这些性能方面的建议,还可以学习如何发现自己的一些优化点。

这些改进只是一个开始。 新的Ignition和TurboFan管道为进一步优化铺平了道路,这些优化将在未来几年内提升JavaScript性能并缩小V8在Chrome和Node.js中的占用空间。

length函数。

有一种叫去优化的安全措施来进行相反的转换,并在假设引擎无效的情况下返回未优化的代码。

易于实现和理解及时反馈更合适的编程环境

在这种情况下,“x”存储在偏移0处,这意味着当将存储器中的 point 对象视为连续缓冲区时,第一偏移将对应于属性 “x”。 V8 还将使用 “类转换” 更新 “C0” ,该类转换指出如果将属性 “x” 添加到 point 对象,则隐藏类应从 “C0” 切换到 “C1”。 下面的 point 对象的隐藏类现在是“C1”。

接下来两个Tip是对之前两种说法的反驳。由于现代V8引擎的改变,这两种说法已经不成立了。

那么隐藏类和内联缓存的概念如何相关呢?无论何时在特定对象上调用方法时,V8 引擎都必须执行对该对象的隐藏类的查找,以确定访问特定属性的偏移量。在同一个隐藏类的两次成功的调用之后,V8 省略了隐藏类的查找,并简单地将该属性的偏移量添加到对象指针本身。对于该方法的所有下一次调用,V8 引擎都假定隐藏的类没有更改,并使用从以前的查找存储的偏移量直接跳转到特定属性的内存地址。这大大提高了执行速度。

总结一下,解释器简单、启动快,但是执行慢。

现在,假设对于p1和p2,将使用相同的隐藏类和转换。那么,对于“p1”,首先添加属性“a”,然后添加属性“b”。然而,“p2”首先分配“b”,然后是“a”。因此,由于不同的转换路径,“p1”和“p2”以不同的隐藏类别结束。在这种情况下,以相同的顺序初始化动态属性好得多,以便隐藏的类可以被重用。

for…in循环比函数迭代、带箭头函数的函数迭代和for循环中的object.keys快4-6倍。

JavaScript是一种基于原型的语言:没有使用克隆过程创建类和对象。JavaScript也是一种动态编程语言,这意味着可以在实例化后轻松地在对象中添加或删除属性。

理解JavaScript的工作原理是写出高效JavaScript代码的关键。忘记那些无关紧要的毫秒级改进:错误地使用对象属性可能导致简单的一行代码速度降低7倍。

一旦 “new Point” 调用发生,V8 将创建一个名为 “C0” 的隐藏类。

Tip1: 在构造函数中声明对象属性

主线程执行你所期望的操作:获取代码、编译代码并执行它 还有一个单独的线程用于编译,因此主线程可以在前者优化代码的同时继续执行 一个 Profiler 线程,它会告诉运行时我们花了很多时间,让 Crankshaft 可以优化它们 一些线程处理垃圾收集器

看下面这个例子:

以为实现JavaScript引擎的流行项目的列表:

TurboFan的优化通过减轻糟糕的JavaScript的影响来提高JavaScript的网络性能。然而,了解这些优化可以提供进一步的加速。

垃圾收集

下面,我们将阐述两种方法的优点和缺点。

接下来,Crankshaft  从另一个线程开始优化。它将 JavaScript 抽象语法树转换为被称为 Hydrogen 的高级静态单分配表示,并尝试优化 Hydrogen 图,大多数优化都是在这个级别完成的。

Tip3:修复函数参数类型

对于垃圾收集,V8采用传统的 mark-and-sweep 算法 来清理旧一代。 标记阶段应该停止JavaScript执行。 为了控制GC成本并使执行更稳定,V8使用增量标记:不是遍历整个堆,尝试标记每个可能的对象,它只是遍历堆的一部分,然后恢复正常执行。下一个GC停止将从上一个堆行走停止的位置继续,这允许在正常执行期间非常短暂的暂停,如前所述,扫描阶段由单独的线程处理。

Tip5:使用for…in

由于使用字典查找内存中对象属性的位置效率非常低,因此 V8 使用了不同的方法:隐藏类。隐藏类与 Java 等语言中使用的固定对象的工作方式类似,只是它们是在运行时创建的。现在,让我们看看他们实际的例子:

结论

对象属性的顺序:始终以相同的顺序实例化对象属性,以便可以共享隐藏的类和随后优化的代码。 动态属性: 因为在实例化之后向对象添加属性将强制执行隐藏的类更改,并降低之前隐藏类所优化的所有方法的执行速度,所以在其构造函数中分配所有对象的属性。 方法:重复执行相同方法的代码将比仅执行一次的多个不同方法的代码运行得更快。 数组:避免稀疏数组,其中键值不是自增的数字,并没有存储所有元素的稀疏数组是哈希表。这种数组中的元素访问开销较高。另外,尽量避免预分配大数组。最好是按需增长。最后,不要删除数组中的元素,这会使键值变得稀疏。 标记值:V8 使用 32 位表示对象和数值。由于数值是 31 位的,它使用了一位来区分它是一个对象还是一个称为 SMI整数。那么,如果一个数值大于 31 位,V8会将该数字装箱,把它变成一个双精度数,并创建一个新的对象来存放该数字。尽可能使用 31 位有符号数字,以避免对 JS 对象的高开销的装箱操作。

JIT将热代码片段发送给基线编译器,尽可能的复用编译后的代码。

一旦第一个语句“this.x = x”被执行,V8 将创建一个名为 “C1” 的第二个隐藏类,它基于“C0”。 “C1”描述了可以找到属性 x 的存储器中的位置。

在看到四种不同的对象形状后,该函数会变成megamorphic,TurboFan将不会再尝试优化这个函数。

V8 引擎也在内部使用多个线程:

JIT需要存储优化的机器代码和探查器的执行信息等,自然会引入内存开销。尽管这一点无法通过优化的JavaScript来改善,但激发了V8的解释器。

当第一次执行 JavaScript 代码时,V8 利用 full-codegen 编译器,直接将解析的 JavaScript 翻译成机器代码而不进行任何转换。这使得它可以非常快速地开始执行机器代码。请注意,V8 不使用中间字节码,从而不需要解释器。

第9行过后,TurboFan将不会再优化add这个函数。

这意味着 V8 整体上将有更简单和更易维护的架构。

编译器的优点、缺点

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

2010 – Crankshaft

因此,属性值可以作为连续缓冲区存储在存储器中,每个缓冲区之间具有固定偏移量, 可以根据属性类型轻松确定偏移的长度,而在运行时可以更改属性类型的 JavaScript 中这是不可能的。

  • Ignition

Ignition and TurboFan

与控制流图不同的是,节点海可以放宽大多数操作的评估顺序。与CGF一样,有状态操作的控制边和效果边在需要时会约束执行顺序。

当语句 “this.y = y” 被执行时,会重复同样的过程(在 “Point” 函数内部,“this.x = x”语句之后)。

随着复杂性的增加,编译器可以进行全局优化。这为编译器提供了比解释器唯一的优势 —— 更快的执行时间。

当代码已经运行一段时间后,分析线程已经收集了足够的数据来判断应该优化哪个方法。

即时编译器尝试结合了解释器和编译器的优点,使代码转换和执行都变得更快。

在Java中,所有对象属性都是在编译之前由固定对象布局确定的,并且无法在运行时动态添加或删除。

具有隐藏类和内联缓存,快速遍历AST的编译器缺点:无优化的即时编译

在 V8 的 5.9 版本出来之前,V8 引擎使用了两个编译器:

本文由10bet发布于Web前端,转载请注明出处:Js工作原理:V8编译器的优化10bet:

关键词:

上一篇:自己动手实现一个axios

下一篇:没有了

最火资讯