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. SSIS SQL Server配置自动作业

    目录: 一. 用SSMS配置作业,自助调度: 二.用SSMS调SSIS包: 一. 用SSMS配置作业,自助调度: 为验证数据,先创建一个表: CREATE TABLE test_table (id I ...

  2. Deutsch lernen (01)

    Was macht Martin? - Um 8.00 Uhr steht martin auf. aufstehen - aufstand - ist aufgestanden 起床 Um 6 Uh ...

  3. dubbo之多版本

    当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用. 可以按照以下的步骤进行版本迁移: 在低压力时间段,先升级一半提供者为新版本 再将所有消费者升级为新版本 然后将剩下的 ...

  4. Visual Studio UI Automation 学习(一)

    这几天需要研究自动化测试工具,因为团队开发使用visual studio,所以需要研究一下Visual studio自带的框架. 刚开始安装的时候,没有选自定义安装,所以安装完成后没有找到UI Aut ...

  5. HDU_1847_基础博弈sg函数

    Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  6. Java中StringTokenizer类的使用

    StringTokenizer是一个用来分隔String的应用类,相当于VB的split函数. 1.构造函数 public StringTokenizer(String str) public Str ...

  7. windows控制台(console)乱码

    在cmd中输入 CHCP 65001,实操有效.记录一下 永久设置,代码如下: Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Cons ...

  8. C# 常用语句

    var list = dt.AsEnumerable().Select(t => t.Field<string>("Bed")).ToList();Select( ...

  9. 2019 gplt团体程序设计天梯赛总结

    分很菜… 以后写题一定记得把题意理清楚了再开始写. 模拟题还是大坑,代码还是写得不够多,代码量一大就写bug. 补题 l1-8 估值一亿的AI核心代码 补题链接:https://pintia.cn/p ...

  10. WERTYU(WERTYU, UVa10082)

    把手放在键盘上时,稍不注意就会往右错一 位.这样,输入Q会变成输入W,输入J会变成输 入K等.键盘如图所示. 输入一个错位后敲出的字符串(所有字母均大写),输出打字员本来想打出的句子.输入保 证合法, ...