V8 引擎是如何工作的?

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

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

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

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

它不仅仅是为了让你现在的应用运行得更快,它是为了将不可能变为可能。

Hidden class

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

举个例子,我们创建一个Point构造函数和两个Point对象:

如果布局相同,以这个例子为例,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 创建)。

每次创建一个新的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?

V8 引擎是如何工作的?的更多相关文章

  1. JavaScript深入浅出第4课:V8引擎是如何工作的?

    摘要: 性能彪悍的V8引擎. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一等 ...

  2. JavaScript工作机制:V8 引擎内部机制及如何编写优化代码的5个诀窍

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

  3. How Javascript works (Javascript工作原理) (二) 引擎,运行时,如何在 V8 引擎中书写最优代码的 5 条小技巧

    个人总结: 一个Javascript引擎由一个标准解释程序,或者即时编译器来实现. 解释器(Interpreter): 解释一行,执行一行. 编译器(Compiler): 全部编译成机器码,统一执行. ...

  4. JavaScript是如何工作的02:深入V8引擎&编写优化代码的5个技巧

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

  5. 深入浏览器工作原理和JS引擎(V8引擎为例)

    浏览器工作原理和JS引擎 1.浏览器工作原理 在浏览器中输入查找内容,浏览器是怎样将页面加载出来的?以及JavaScript代码在浏览器中是如何被执行的? 大概流程可观察以下图: 首先,用户在浏览器搜 ...

  6. Chrome V8引擎系列随笔 (1):Math.Random()函数概览

    先让大家来看一幅图,这幅图是V8引擎4.7版本和4.9版本Math.Random()函数的值的分布图,我可以这么理解 .从下图中,也许你会认为这是个二维码?其实这幅图告诉我们一个道理,第二张图的点的分 ...

  7. [翻译] V8引擎的解析

    原文:Parsing in V8 explained 本文档介绍了 V8 引擎是如何解析 JavaScript 源代码的,以及我们将改进它的计划. 动机 我们有个解析器和一个更快的预解析器(~2x), ...

  8. (译)V8引擎介绍

    V8是什么? V8是谷歌在德国研发中心开发的一个JavaScript引擎.开源并且用C++实现.可以用于运行于客户端和服务端的Javascript程序. V8设计的初衷是为了提高浏览器上JavaScr ...

  9. V8引擎嵌入指南

    如果已读过V8编程入门那你已经熟悉了如句柄(handle).作用域(scope)和上下文(context)之类的关键概念,以及如何将V8引擎作为一个独立的虚拟机来使用.本文将进一步讨论这些概念,并介绍 ...

随机推荐

  1. 【MySQL】通信协议

    1.TCP/IP(Transmission Control Protocol/Internet Protocol) 该通信协议套件用于连接 Internet 上的主机.在 Linux 操作系统中,TC ...

  2. ASP.NET Cache 实现依赖Oracle的缓存策略

    ASP.NET 中的缓存提供了对SQL依赖项的支持,也就是说当SQL SERVER数据库中的表或行中的数据被更改后,缓存中的页面就失效,否则,页面输出可一直保留在缓存当中.这确实为程序员提供了方便.但 ...

  3. 安卓代码迁移:Program "sh" not found in PATH

    Description    Resource    Path    Location    Type  Program  "sh"  not  found in PATH 参考链 ...

  4. css3基础篇一

    CSS3 选择器 选择器 例子 例子描述 CSS .class .intro 选择 class="intro" 的所有元素. 1 #id #firstname 选择 id=&quo ...

  5. 揭开jQuery的面纱

    简单地说,jQuery是一个优秀的JavaScript类库,也就是使用JavaScript面向对象的性质编写的一个JavaScript类的集合.jQuery究竟能为我们提供哪些功能呢?简单地说可以从七 ...

  6. 微信小程序—picker(滚动选择器)

    官方api:https://mp.weixin.qq.com/debug/wxadoc/dev/component/picker.html 上边是官网的api.小程序中,底部下拉滚动选择主要有这几种 ...

  7. node、Mongo项目如何前后端分离提供接口给前端

    node接口编写,vue-cli代理接口方法  通常前端使用的MocK 数据的方法,去模拟假的数据,但是如果有node Mongodb 去写数据的话就不需要在去mock 数据了,具体的方法如下. 首先 ...

  8. BZOJ 4712: 洪水 挖坑待补

    Code: #include<bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) # ...

  9. pymysql.err.ProgrammingError: (1064)(字符串转译问题)

    代码: sql = "insert into dm_copy(演出类型,演出场馆,剧目名称,演出地点,演出时间,演出票价,演出团体,创建时间, url)values('%s','%s','% ...

  10. 【剑指Offer】17、树的子结构

      题目描述:   输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构)   解题思路:   要查找树A中是否存在和树B结构一样的子树,我们可以分为两步:第一步, ...