JavaScript 引擎 V8 简介
概念:V8是用C ++编写的Google开源高性能JavaScript和WebAssembly引擎(执行JavaScript代码的程序或解释器)。它用于Chrome和Node.js等。它实现ECMAScript和WebAssembly,并在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32,ARM或MIPS处理器的Linux系统上运行。V8可以独立运行,也可以嵌入到任何C ++应用程序中。
特性:
- V8是Google Chrome浏览器内置的JavaScript脚本引擎。
- Google Chrome使用V8的API,但引擎的内核部分是独立于浏览器之外的。
- V8引擎编译和执行JavaScript源代码。
- 速度是V8追求的主要设计目标之一,它把JavaScript代码直接编译成机器码运行,比起传统的“中间代码+解释器”的引擎,优势不言而喻。
- V8的团队说Chrome对脚本的解析和执行速度是Firefox和Safari的10倍,是IE的56倍。
- V8支持众多操作系统,如windows、linux、android等,也支持其他硬件架构,如IA32,X64,ARM等,具有很好的可移植和跨平台特性。
- V8使用C++开发,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。
JavaScript 引擎 V8 历史及由来
问题驱动一:在过去几年,JavaScript在很多领域得到了广泛的应用,然而限于JavaScript语言本身的不足,执行效率不高。Google也推出了一些JavaScript网络应用,如Gmail、Google Maps及Google Docs office等。这些应用的性能不仅受到服务器、网络、渲染引擎以及其他诸多因素的影响,同时也受到JavaScript本身执行速度的影响。然而既有的JavaScript引擎无法满足新的需求,而性能不佳一直是网络应用开发者最关心的。Google就开始了V8引擎的研究,将一系列新技术引入JavaScript引擎中,大大提高了JavaScript的执行效率。
历史:
- 2006 年秋天,谷歌聘请 Lars Bak 为 Chrome 浏览器构建一个新的 Java 引擎,当时它还只是谷歌内部的一个秘密项目。后来,Lars 从硅谷回到了丹麦的奥胡斯。Lars 想留在丹麦,但那里没有谷歌办事处,于是 Lars 和几个最初参与该项目的工程师开始在他的农场办公。新的 Java 运行时被命名为“V8”,灵感源自 50 年代经典的“肌肉车”的引擎。后来,随着 V8 团队不断成长,开发者从农场搬到了奥胡斯的一个现代化的办公大楼里。然后,整个团队开始专注于构建地球上最快的 Java 运行时。
- 2008 年 9 月 2 日,V8 与 Chrome 在同一天宣布开源。最初的代码提交日期可追溯到 2008 年 6 月 30 日。在那之前,V8 是在一个私有 CVS 存储库上开发的。最初,V8 只支持 ia32 和 ARM 指令集,并使用 SCons 作为构建系统。
- 2009 年,V8 引入一个名为 Irregexp 的正则表达式引擎,改进了真实世界的正则表达式性能。随着 x64 移植的引入,支持的指令集数量从两个增加到三个。2009 年,内嵌 V8 的 Node.js 发布了第一个版本。在最初的 Chrome 漫画中明确提到了将 V8 嵌入到非浏览器项目中的可能性,而 Node.js 做到了!Node.js 成为最受欢迎的 Java 生态系统之一。
- 2010 年,V8 引入了全新的优化 JIT 编译器 Crankshaft,从而极大提升了运行时性能。Crankshaft 生成的机器代码比之前的 V8 编译器(未命名的)快两倍,而体积小了 30%。同年,V8 增加了第四个指令集:32 位 MIPS。
- 2011 年,垃圾回收器性能得到了极大的改善。新的增量垃圾回收器大大减少了停顿时间,同时保持了极佳的峰值性能和低内存使用率。V8 引入了隔离的概念,可以在一个进程中启动多个 V8 运行时实例,为在 Chrome 中实现轻量级的 Web Worker 铺平了道路。后来我们从 SCons 转向 GYP,这是第一次进行 V8 构建系统迁移。我们实现了对 ES5 strict mode 的支持。与此同时,开发工作从奥胡斯移交到了德国慕尼黑。
- 2012 年是 V8 项目的基准测试年。团队通过不断的速度冲刺迭代来优化 V8 的性能,并使用 Sunspider 和 Kraken 基准套件来测量性能。后来,我们自己开发了一个名为 Octane 的基准测试套件(其核心是 V8 Bench),它引领了峰值性能竞赛,促进了所有主要 JS 引擎的运行时和 JIT 技术的大幅改进。所有这些努力导致的一个结果是从随机抽样转向了一种基于确定性和计数的技术,用于检测 V8 运行时分析器中的“热”函数。
- 2013 年,一个名为 asm.js 的 Java 子集出现了。由于 asm.js 只支持静态类型的算术运算、函数调用和基本类型的堆访问,因此 asm.js 代码的运行性能是可预测的。我们发布了新版 Octane(Octane 2.0),对现有基准测试进行了更新,并针对 asm.js 等用例增加新的基准测试。Octane 促进了编译器优化的发展,例如分配折叠和用于类型转换和预定的基于分配站点的优化,大大提高了峰值性能。作为内部“Handlepocalypse”计划的一部分,我们对 V8 Handle API 进行了彻底重写,提升其易用性和安全性。同年,Chrome 实现的 TypedArrays 从 Blink 转到了 V8 中。
- 2014 年,V8 通过并发编译将 JIT 编译的一些工作从主线程中移除,以此来减少堵塞,并显著提升了性能。后来,我们推出了名为 TurboFan 的新优化编译器的初始版本。同时,我们的合作伙伴将 V8 移植到三个新的指令集架构上:PPC、MIPS64 和 ARM64。继 Chromium 之后,V8 转向了另一个构建系统 GN。V8 的测试基础设施得到了显著改进,可以使用 Tryserver 在构建开始之前测试每个补丁。在源代码控制方面,V8 从 SVN 迁移到了 Git。
- 2015 年是 V8 最繁忙一年,我们实现了代码缓存和脚本流,大大加快了网页加载速度。那年晚些时候,我们开始了新解释器 Ignition 的开发工作。我们尝试了 strong mode,打算让它成为 Java 的子集,以实现更强和更可预测的性能保证。我们实现了 strong mode,但后来发现它所带来的好处并不足以抵消我们所花费的成本。而我们添加的提交队列却大大提高了生产力和稳定性。V8 的垃圾回收器也开始与 Blink 等嵌入器合作,以便在空闲时进行垃圾回收。空闲时垃圾回收显著减少了垃圾回收停顿和内存消耗。当年的 12 月分,第一个 WebAssembly 原型来到了 V8 上。
- 2016 年,我们发布了最后一组 ES2015(以前称为“ES6”)特性集(包括 promise、类语法、词法作用域、解构等),以及一些 ES2016 特性。我们还开始推出新的 Ignition 和 TurboFan 管道,用它来编译和优化 ES2015 和 ES2016 特性,并将 Ignition 作为低端 Android 设备的默认配置。我们在 PLDI 2016 大会上展示了我们的空闲时垃圾回收工作成功。
- 我们启动了 Orinoco 项目,这是一个针对 V8 的并发垃圾回收器,旨在减少主线程垃圾回收时间。我们将性能工作从合成微基准转向了测量和优化实际性能。出于调试的目的,V8 检查器从 Chromium 迁移到了 V8,允许任何 V8 嵌入器(不仅仅是 Chromium)使用 Chrome DevTools 来调试在 V8 中运行的 Java。WebAssembly 从原型转为了实验支持。V8 获得了 ACM SIGPLAN 编程语言软件大奖。在这一年还新增了另一个移植平台:S390。
- 2017 年,我们终于完成了对 V8 引擎进行的重大修整,默认情况下启用新的 Ignition 和 TurboFan 管道。这样就有可能在后续从代码库中移除 Crankshaft(130,380 个已删除的代码行)和 Full-codegen。我们推出了 Orinoco v1.0,包括并发标记、并发扫描、并行清理和并行压缩。我们正式将 Node.js 视为 V8 的一等嵌入器。从那以后,如果某些 V8 补丁无法通过 Node.js 测试套件的测试,就不能推出这些补丁。我们的基础设施提供了正确模糊测试支持,确保任何代码都能产生一致的结果,无论是在怎样的配置下运行。
- 在一个面向整个业界的协调启动发布中,V8 默认启用了 WebAssembly。我们实现了对 Java 模块以及 ES2017 和 ES2018 完整特性集的支持(包括异步函数、共享内存、异步迭代,rest/spread 属性和 RegExp)。
- 我们提供了对 Java 代码覆盖的原生支持,并启动了 Web Tooling Benchmark,用以帮助我们衡量 V8 的优化对实际开发者工具以及这些工具所生成的 Java 输出的性能的影响。我们可以借助用于跟踪 Java 对象到 C++ DOM 对象的包装器来解决 Chrome 中长期存在的内存泄漏问题,并有效地处理 Java 和 Blink 堆上对象的闭包传递。我们后来使用这个基础设施来提升开发者工具的堆快照能力。
- 2018 年,一个行业范围的安全事件(Spectre/Meltdown 漏洞)颠覆了我们对 CPU 信息安全的认知。V8 工程师进行了大量有关安全攻击问题的研究,以便更好地理解托管语言所面临的威胁并提出解决措施。V8 为运行不受信任代码的嵌入器提供了针对 Specter 和类似侧通道攻击的缓解措施。
- 最近,我们为 WebAssembly 发布了一个名为 Liftoff 的基线编译器,它大大减少了 WebAssembly 应用程序的启动时间,同时提供了可预测的性能。我们发布了 BigInt,一个新的 Java 原始类型,可以实现任意精度的整数。我们实现了嵌入式内置函数,并可以对它们进行惰性反序列化,从而显著降低 V8 多个隔离的占用空间。现在可以让后台线程编译脚本字节码。我们启动了 Unified V8-Blink Heap 项目,可同步运行跨 V8 和 Blink 的垃圾回收。
- 未完待续
JavaScript 引擎 V8 最新版
JavaScript 引擎 V8 发布了 8.3 版本(测试阶段),正式版本将在之后随 Chrome 83 一起推出。8.3 版本带来了一些面向开发人员的特性,主要亮点包括:
性能
垃圾收集器中更快的 ArrayBuffer 跟踪
ArrayBuffer 的后备存储是使用嵌入器提供的 ArrayBuffer::Allocator 在 V8 堆之外分配的。当垃圾收集器回收其 ArrayBuffer 对象时,需要释放这些后备存储。V8 v8.3 具有跟踪 ArrayBuffer 及其后备存储的新机制,该机制允许垃圾回收器迭代并同时将后备存储释放给应用程序。这将 ArrayBuffer 繁重的工作负载中的总 GC 暂停时间减少了 50%。
更大的 Wasm 内存
根据 WebAssembly 规范的更新,V8 v8.3 现在允许模块请求最大为 4GB 的内存,从而允许将更多内存密集型用例引入 V8 驱动的平台。要注意的是,这么多的内存可能并不总是在用户的系统上可用;建议以较小的大小创建内存,根据需要进行扩展,并适当地处理增长失败的情况。
修复
存储到原型链上具有类型数组的对象
根据 JavaScript 规范,当将值存储到指定键时,需要查找原型链,以查看键是否已存在于原型中。这些密钥通常不存在于原型链中,因此 V8 安装了快速查找处理程序。
但最近在某些特殊情况中,V8 错误地安装了此快速查找处理程序,从而导致了错误的行为。当 TypedArray 在原型链上时,所有存储到 TypedArray 的 OOB 的键都应被忽略。例如,在低于 v[2] 的情况下,不应向 v 添加属性,并且后续读取应返回 undefined。
v = {}; v.__proto__ = new Int32Array(1); v[2] = 123; return v[2]; // Should return undefined
V8 的快速查找处理程序无法处理这种情况,因此在上例中,将返回 123 。V8 v8.3 通过在 TypedArrays 在原型链上时不使用快速查找处理程序来解决此问题。这种情况并不常见,在基准测试中尚未发现任何性能下降的情况。
更新说明:https://v8.dev/blog/v8-release-83