JavaScript引擎 V8 的前世今生

2,586 阅读12分钟
原文链接: mp.weixin.qq.com

写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。

本月不仅仅是 Chrome 的 10 周年,同时也是 V8 开源的 10 周年。这篇文章概述了 V8 在保密阶段和开源后 10 年的主要里程碑。

V8 开源之前

2006 年秋,Google 聘请 Lars Bak 为 Chrome 浏览器构建了一个新的 JavaScript 引擎,当时它仍然是一个 Google 内部秘密的项目。后来,Lars 想留在丹麦,他从硅谷搬回了丹麦的奥胡斯。但那里没有谷歌办公室,Lars和该项目最早的几个工程师开始在他的农场里工作。新的 JavaScript 引擎被命名为“V8”,隐喻肌肉车里的强大引擎[注:肌肉车装备有大马力 V8 发动机]。随着 V8 团队壮大,开发人员从他们的农场搬到了奥胡斯的现代化办公大楼,团队带着他们特有的驱动力,专注于构建地球上最快的 JavaScript 引擎。

V8 开源并持续改进

2008 年 9 月 2 日,Chrome 发布,同时 V8 正式开源。最初的提交日期可追溯到 2008 年 6 月 30 日。在此之前,V8 在私有的 CVS 仓库中开发。最初,V8 仅支持 ia32 和 ARM 指令集,并使用 SCons 作为其构建系统。

2009 年推出了一款名为 Irregexp 的全新正则表达式引擎,提升了正则表达式在实际工作中的性能。随着 x64 的引入,支持的指令集数量从两个增加到三个。2009 年,基于 V8 的 Node.js 项目的第一个版本发布。从最初的 Chrome 中提出的在非浏览器项目嵌入 V8 的设想,到 Node.js,它确实实现了!Node.js 如今成为最受欢迎的 JavaScript 生态系统之一。

2010 年,V8 引入了全新优化的 JIT 编译器,从而大大提升了运行时性能。Crankshaft 生成的机器代码比前一个(未命名的)V8 编译器快两倍,小 30%。同年,V8 增加了第四个指令集:32 位 MIPS。

2011 年,垃圾回收大大的得到了改善。新的增量垃圾收集器大大减少了暂停时间,同时保持了极佳的峰值性能和低水平的内存使用率。V8 引入了隔离(Isolates)的概念,它允许嵌入器在一个进程中启动 V8 的多个运行时实例,为 Chrome 中的轻量级 Web Workers 铺平了道路。在当我们从 SCons 转换到 GYP 时,我们第一次迁移了 V8 的两个构建系统,实现了对 ES5 严格模式的支持。与此同时,开发工作从奥胡斯搬到慕尼黑(德国),在新的领导下,与奥胡斯的原始团队进行了大量的跨团队交流。

2012 年是 V8 项目的基准测试年。通过 Sunspider 和 Kraken 基准套件测量,该团队通过速度冲刺来优化 V8 的性能。后来,我们开发了一个名为 Octane 的新基准测试套件(其核心是 V8 Bench),它将最高性能竞争带到了最前沿,并促进了所有主要 JS 引擎中运行时和 JIT 技术的大幅改进。这些努力的一个结果是在检测 V8 运行时分析器中的“热”函数时从随机抽样切换到确定性的基于计数的技术。这减少了某些页面加载(或基准测试运行)随机地比其他页面加载速度慢得多的现象。

2013 年见证了一个名为 asm.js 的 JavaScript 子集。 由于 asm.js 仅限于静态类型的运算,函数调用和仅具有基本类型的堆访问,因此经验证的 asm.js 代码可以以可预测的性能运行。我们发布了新版 Octane(Octane 2.0),其中包含对现有基准测试的更新以及针对 asm.js 等用例的新基准测试。Octane 促进了新的编译器优化的发展,例如分配折叠和基于分配站点的优化,用于类型转换和预定,大大提高了峰值性能。 作为努力的一部分我们内部称之为“Handlepocalypse”,V8 Handle API 被完全重写,以便更容易正确和安全地使用。 同样在 2013 年,Chrome 在 JavaScript 中实现的 TypedArrays 已从 Blink 转移到 V8。

2014 年,V8 通过并发编译将 JIT 编译的一些工作从主线程中移除,减少了卡顿并显著提高了性能。 那年下半年,我们发布了为 TurboFan 的新优化编译器的初始版本。同时,我们的合作伙伴将 V8 移植到三个新的指令集架构:PPC,MIPS64 和 ARM64。 继 Chromium 之后,V8 转向另一个构建系统 GN。 V8 测试基础架构得到了显著改进,现在可以使用 Tryserver 在构建前测试各种构建机器上的每个补丁。对于源代码控制,V8 从 SVN 迁移到 Git。

2015 年是 V8 在多个方面出击的一年。我们实现了代码缓存和脚本流,大大加快了网页加载时间。在 ISMM 2015 发布运行时系统使用分配区域的工作。稍后,我们开始了一个名为 Ignition 的新解释器的工作。我们尝试了使用强模式对 JavaScript 进行子集化的想法,以实现更强的保证和更可预测的性能。我们再标记后实现增强模式,但是后来发现收益小于所消耗的代价。添加提交队列大大提高了生产力和稳定性。V8 的垃圾回收器也开始与 Blink 等嵌入器合作,以便在闲置期间安排垃圾回收工作。空闲时垃圾回收显著减少了显示的垃圾回收卡顿和内存消耗。 12 月,第一个 WebAssembly 原型在 V8 中采用。

2016 年,我们发布了最新的 ES2015(以前称为“ES6”)功能集(包括 promises, class 语法, lexical scoping, 解构等),以及一些 ES2016 功能。我们还开始推出新的 Ignition 和 TurboFan 管道,使用它来编译和优化 ES2015 和 ES2016 功能,并默认为低端 Android 设备发送 Ignition。我们在 PLDI 2016 上展示了我们在闲置时垃圾回收方面的取得的成功。我们启动了 Orinoco 项目,这是一个针对 V8 的新的主并行和并发垃圾收集器,以减少主线程垃圾收集时间。我们将注意力从合成微基准转移,开始发力测量和优化实际性能。为了进行调试,V8 检查器已从 Chromium 迁移到 V8,允许任何 V8 嵌入器(而不仅仅是 Chromium)使用 Chrome DevTools 来调试在 V8 中运行的 JavaScript。WebAssembly 从原型过渡到实验支持,与其他浏览器厂商协调 WebAssembly 的实验支持。V8 获得了 ACM SIGPLAN 编程语言软件奖。并添加了另一个平台:S390。

2017 年,我们终于完成了对引擎进行的重大修整,默认情况下启用了新的 Ignition 和 TurboFanpipeline。这使得以后可以从代码库中删除 Crankshaft(130,380 行已删除的代码)和 Full-codegen。我们推出了 Orinoco v1.0,包括并发标记,并发扫描,并行清理和并行压缩。 我们正式将 Node.js 视为伴随 Chromium 的一流 V8 嵌入器。从那以后,如果 V8 补丁不能通过 Node.js 测试套件,这些补丁就不能发布。我们的基础架构获得了对正确模糊测试的支持,确保任何代码都能产生一致的结果,无论其运行的配置如何。

在整个行业的协调发布中,V8 默认支持 WebAssembly。 我们实现了对 JavaScript 模块以及完整 ES2017 和 ES2018 功能集的支持(包括异步函数,共享内存,异步迭代,rest/spread 属性和 RegExp 功能)。我们提供了对 JavaScript 代码覆盖的原生支持,并启动了 Web Tooling Benchmark,以帮助我们衡量 V8 的优化对实际开发工具的性能及其产生的 JavaScript 的影响。包装器从 JavaScript 对象跟踪到 C++ DOM 对象并返回允许我们解决 Chrome 中长期存在的内存泄漏问题,并有效地处理 JavaScript 和 Blink 堆上对象的闭包传递。我们后来使用此基础结构来增加堆快照开发人员工具的功能。

2018 年,一个行业范围的安全事件颠覆了我们认为我们对 CPU 信息安全的了解,并公开披露了 Spectre/Meltdown 漏洞。V8 工程师进行了广泛的攻击性研究,以帮助理解托管语言的威胁并开发缓解措施。针对运行不受信任代码的嵌入器,V8 为对抗 Specter 和类似的侧通道攻击提供了缓解措施。

最近,我们为 WebAssembly 发布了一个名为 Liftoff 的基线编译器,它大大减少了 WebAssembly 应用程序的启动时间,同时仍然实现了可预测的性能。我们发布了 BigInt,这是一个新的 JavaScript 原始类型,可以实现任意精度的整数。我们实现了嵌入式内置函数,并且可以对它们进行延迟反序列化,从而显著降低 V8 的多个隔离区的占用空间。我们可以在后台线程上编译脚本字节码。我们启动了 Unified V8-Blink Heap 项目,以同步方式运行跨组件 V8 和 Blink 垃圾收集。而今年还没有结束......

性能起伏的 V8

多年来 Chrome 的 V8 Bench 评分显示了 V8 变化对性能的影响。(我们正在使用 V8 Bench,因为它是可以在最初的 Chrome 测试版中运行的少数基准测试之一。)

从 2008 年至 2018 年 Chrome V8 的 Bench 得分

在过去十年中,我们在这个基准测试中的得分上升了 4 倍!

但是,您可能会注意到这些年来有两次性能下降。两者都很有趣,因为它们对应于 V8 历史上的重大事件。2015 年的性能下降发生在 V8 发布 ES2015 基础特性时。这些功能在 V8 代码库中是交叉的,因此我们专注于正确性而不是初始版本的性能。我们接受了这些微小的速度回归,以尽快为开发人员提供新特性。在 2018 年初,幽灵漏洞的出现,V8 发布了缓解措施以保护用户免受潜在攻击,导致性能再次下降。幸运的是,现在 Chrome 正在发布站点隔离,我们可以再次禁用缓解措施,从而恢复性能。

另一个图表是它在 2013 年左右开始趋于平稳。这是否意味着 V8 放弃并停止在性能优化方面的投入? 恰恰相反! 图表的扁平化代表了 V8 团队从合成微基准(如 V8 Bench 和 Octane)到优化实际性能的重心转移。V8 Bench 是一个旧的基准测试,不使用任何现代 JavaScript 功能,也不接近实际的生产代码。与最近的 Speedometer 基准套件相比:

 

从 2013 年至 2018 年 Chrome 的 Speedometer 1 得分

尽管从 2013 年到 2018 年,V8 Bench 的改进程度微乎其微,但在同一时期,我们的 Speedometer 1 得分(另一个)上升了 4 倍。 (我们使用了Speedometer 1,因为 Speedometer 2 使用了 2013 年尚未支持的现代 JavaScript 功能。)

如今,我们有更好的基准测试,可以更准确地反映现代 JavaScript 应用程序,最重要的是,我们会主动测量和优化现有的 Web 应用程序。

总结

尽管 V8 最初是为 Chrome 构建的,但它一直是一个独立的项目,具有单独的代码库和嵌入式 API,允许任何程序使用其 JavaScript 执行服务。在过去的十年里,该项目的开放性使其不仅成为 Web 平台的关键技术,而且成为 Node.js 等其他环境中的关键技术。在此过程中,尽管有许多变化和显着的增长,但项目仍在不断发展。

最初,V8 仅支持两个指令系统。在过去的 10 年里,支持的平台达到了 8个:ia32,x64,ARM,ARM64,32 和 64 位 MIPS,64 位 PPC 和 S390。V8 的构建系统从 SCons 迁移到 GYP 到 GN。该项目从丹麦迁至德国,目前在全球范围内都拥有工程师,包括伦敦,山景城和旧金山,以及来自其他地方的 Google 以外的贡献者。 我们将整个 JavaScript 编译管道从未命名的组件转换为 Full-codegen(基线编译器)和 Crankshaft(反馈驱动的优化编译器)到 Ignition(一个解释器)和 TurboFan(一个更好的反馈驱动的优化编译器)。V8 从只是 JavaScript 引擎到同时支持 WebAssembly。JavaScript 语言本身从 ECMAScript 3 演变为 ES2018; 最新的 V8 甚至可以实现 ES2018 之后的特性。

Web 的故事是一个漫长而持久的故事。庆祝 Chrome 和 V8 的 10 岁生日是一个很好的机会来反映:尽管这是一个重要的里程碑,但 Web 平台的故事已经持续了超过 25 年。毫无疑问,Web 的故事至少还会持续很长时间。我们致力于确保 V8,JavaScript 和 WebAssembly 在故事中继续成为有趣的角色。我们很激动想知道下个十年会发生什么?敬请关注!

英文原文:

https://v8project.blogspot.com/2018/09/10-years.html


“UC国际技术”致力于与你共享高质量的技术文章

欢迎关注我们的公众号、将文章分享给你的好友