大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。
本文结构组织和部分内容来自 Antonello Zanini 发表的文章《Top 8 Recent V8 in Node Updates》,同时对部分内容进行了修改和优化,文章链接已经在文末展开。
1. 关于 V8 的介绍1.1 什么是 V8V8 是 Google 开发的高性能 JavaScript 引擎,目的是将 JavaScript 代码在运行时转换为机器代码以供底层硬件执行。 该引擎主要由 Google 的 V8 团队开发,专注于速度和效率,V8 依赖于即时编译(Just-in-Time)并使用内联缓存(Inline Caching)来实现高性能。
内联缓存:将重复的函数调用等于函数返回的值,以节省时间并提高效率或速度,比如:printUserName(userName) = “Hello John Doe”
关于 V8 编译器和优化编译器的发展是很长的一段历史,接下来一起回顾下。
1.2 V8 的发展历史Full-Codegen 和 Crankshaft 退出舞台该版本于 2017 年 4 月 27 日发布,在这之前有 2 个主要的编译器(注意,在 V8 之前,JavaScript 更多的是一种解释语言,即无需事先编译成机器语言):
Full-Codegen :将 JavaScript 直接解析为机器代码,无需中间语言,从而可以更快地开始执行Crankshaft :JIT 编译器为热方法(多次调用)生成优化的代码但是也就是从 V8 5.9 开始,其不再使用 Full-codegen 和 Crankshaft 来执行 JavaScript,因为其不再能够跟上新的 JavaScript 语言功能以及这些功能所需的优化。
Ignition 和 TurboFan 时代2017 年,V8 推出了全新的编译器管道,由 Ignition(解释器)和 TurboFan(优化编译器)组成。
借助 Ignition,V8 将 JavaScript 函数编译为简洁的字节码,其大小仅为等效基线机器代码的 50% ~ 25%。 然后,该字节码由高性能解释器执行,该解释器在现实网站上的执行速度接近 V8 现有基线编译器生成的代码。
2021 年 V8 发布 SparkPlug 非优化编译器Sparkplug 旨在快速编译,可以说非常快,几乎可以随时进行编译,从而使开发者能够比 TurboFan 代码更积极地分层 Sparkplug 代码。
随着 SparkPlug(Non-optimizing JavaScript Compiler) 编译器的发布,引入了新的分层编译管道,其补充了 V8 中现有的 TurboFan 编译器,与 HotSpot 使用的分析 C1 编译器直接并行。
2023 年 V8 发布 Maglev 全新优化编译器2023 年,V8 添加了基于 Maglev SSA 的编译器,其比 Sparkplug 慢 10 倍,但比 TurboFan 快 10 倍,弥补了 Sparkplug 和 TurboFan 之间的差距,适用于频率较低的运行循环,这些循环不会变得 “热” 到足以通过优化 TurboFan。
与大多数 Web 应用程序的情况一样,其与浏览器交互的时间多于 JavaScript 执行的时间。
总之在 V8 程序中,不同级别的优化代码同时并存,而优化编译器才是真正性能提升的地方。得益于 V8,基于 Chromium 的浏览器和 Node.js 可以运行 JavaScript 代码。 Deno 还依赖 V8 来执行 JavaScript,使 V8 成为现代软件开发的主角。
2.V8 JavaScript 引擎最新更新2.1 添加诸多 JavaScript 新特性JavaScript 需要不断发展以满足现代开发的需求,新的标准化功能经常被添加到语言中,JavaScript 引擎则需要实现。
在最新的迭代中,V8 添加了对以下内容的支持:
toWellFormed() 字符串方法:返回一个字符串,其中所有单独代理项 (lone surrogates) 均替换为 Unicode 替换字符 U+FFFDconst strings = [ // Lone leading surrogate "ab\uD800", "ab\uD800c", // Lone trailing surrogate "\uDFFFab", "c\uDFFFab", // Well-formed "abc", "ab\uD83D\uDE04c",];for (const str of strings) { console.log(str.toWellFormed());}// Logs:// "ab�"// "ab�c"// "�ab"// "c�ab"// "abc"// "abc"isWellFormed() 字符串方法:返回一个布尔值以指示字符串是否包含任何单独代理项Resizable ArrayBuffers:ArrayBuffer() 构造函数现在接受 maxByteLength 来定义其最大大小。 然后,开发者可以使用 resize() 方法为可调整大小的 ArrayBuffer 对象分配新的大小,从而使得 ArrayBuffers 更加高效并且更接近 WebAssemblyconst buffer = new ArrayBuffer(8, { maxByteLength: 16});if (buffer.resizable) { console.log("Buffer is resizable!"); buffer.resize(12); // 将 ArrayBuffer 的大小调整为指定大小(以字节为单位)}ArrayBuffer transfer:ArrayBuffer 对象可以通过结构化克隆算法在不同的执行上下文之间传输const buffer = new ArrayBuffer(8);const view = new Uint8Array(buffer);view[1] = 2;view[7] = 4;// Copy the buffer to the same sizeconst buffer2 = buffer.transfer();console.log(buffer.detached); // trueconsole.log(buffer2.byteLength); // 8RegExp v 标志:一种新模式,解锁对扩展字符类(例如:所有表情符号)的支持数组的 Object.groupBy() 静态方法:根据给定的回调函数对给定可迭代的元素进行分组。Array.fromAsync() 静态方法:从异步可迭代 (async iterable)、可迭代 (iterable) 或类数组对象创建新的浅复制 Array 实例。const asyncIterable = (async function* () { for (let i = 0; i < 5; i++) { await new Promise((resolve) => setTimeout(resolve, 10 * i)); yield i; }})();Array.fromAsync(asyncIterable).then((array) => console.log(array));// [0, 1, 2, 3, 4]Promise.withResolvers() :返回一个对象,其中包含一个新的 Promise 对象和两个用于 resolve 或 reject 的函数。另外,动态 import() 已更新为接受 with 选项来指定导入属性,具体可以查看 TC39 的 proposal-import-attributes 提案:
const jsonModule = await import("./data.json", { // 通过 with 对象指定导入的属性 with: {type: "json"},});2.2 支持垃圾收集语言编译为 WebAssemblyWasmGC(WebAssembly Garbage Collection) 代表了 WebAssembly (Wasm) 领域的关键进步,其目标是将垃圾收集语言编译为 WasmGC 构造以使其在 Web 上运行。目前主要有两种方式:
“传统” 移植方法:将语言现有实现编译为 WasmMVP,即 2017 年推出的 WebAssembly 最小可行产品(WebAssembly Minimum Viable Product)WasmGC 移植方法:语言被编译为 Wasm 本身的 GC 构造,这些构造在最近的 GC 提案中定义经过 V8 几年的努力,目前 WasmGC 已经 ready。 Wasm 现在有一种内置方法来分配由 V8 垃圾收集器管理的对象和数组,从而能够将用 Java、Kotlin、Dart、Python、C# 和类似垃圾收集语言编写的应用程序编译为 Wasm。
这是将 GC 语言高效引入 Web 的巨大飞跃!
2.3 全新的优化编译器 Maglev2023 年 V8 引入了一个名为 Maglev 的全新优化编译器 (Optimizing JIT),该编译器位于 Sparkplug 非优化 JavaScript 编译器和 TurboFan 顶级优化编译器之间。
Maglev 生成代码的速度比 Sparkplug 慢近 20 倍,但比 TurboFan 快 10 ~ 100 倍。V8 团队已经注意到 JavaScript 和 WebAssembly 基准网站的以下性能改进:
JetStream +8.2%+6% Speedometer2.4 优化编译器 Turbofan 的新架构Maglev 并不是开发团队在编译器技术上进行的唯一优化, 最新的 V8 还引入了 Turboshaft,这是高级优化编译器 Turbofan 的新内部架构。
这种新的、现代的、设计更好的架构使 Turbofan 通过新的优化和更快的编译更容易扩展。在某些情况下,编译时间会快 2x,不仅可以节省电量还可为未来的其他性能提升铺平道路。
2.5 通过控制流集成提高安全性CFI(Control-flow Integrity) 是一项重要的安全功能,旨在阻止在劫持程序控制流的攻击。 通过对控制流指令的执行方式进行严格控制,CFI 可以防止攻击者利用内存损坏漏洞执行任意代码。 因此,即使攻击者成功破坏了进程的内存,CFI 也会阻止执行未经授权的指令。
V8 中采用 CFI 为安全环境带来了多项好处,显著降低了成功利用内存损坏漏洞的风险,而这些通常是攻击者试图控制系统的主要目标。 通过对正向和反向控制流传输应用完整性检查,CFI 降低了攻击者操纵程序执行以执行恶意代码的可能性。
2.6 更快的 HTML 解析器和 DOM 分配为了追求优化 Web 性能,V8 对 HTML 解析和 DOM 分配(DOM Allocation)进行了重大改进。
性能基准测试中大部分时间都花在解析 HTML 上。 尽管这不是对 V8 的直接增强,但 V8 团队决定应用其在性能优化方面的专业知识,为 Blink 添加更快的 HTML 解析器。 Blink 是 Chromium 使用的渲染引擎,基于 WebKit 的 WebCore 组件。这些变化最终导致 Speedometer 分数提高了 3.4%。
其他优化已应用于 Oilpan(DOM 对象的 V8 分配器)内的 DOM 内存分配策略。 通过引入页池机制(Page Pool Mechanism)并支持压缩和未压缩指针,内核往返成本已大大降低。 此外,通过避免对高流量字段进行压缩,分配工作负载的速度提高了 3x。 这些更新显著改进了 DOM 密集型基准测试,提供了更流畅、响应更灵敏的浏览体验。
2.7 新的 WebAssembly 功能与 JavaScript 一样,针对 Wasm 也在不断添加新功能,主要更新包括:
Wasm 中支持 multi-memory(Wasm 每个模块有多个内存) 来处理多个内存支持尾部调用 (tail-calls) 以执行尾部调用优化支持轻松的 SIMD 操作以释放更高水平的性能针对内存需求大的应用程序的 memory64 的完整实现2.8 通过编译时常量地址优化内存处理V8 最近在内存管理方面引入了革命性的改进,新功能称为静态根(static roots),旨在优化基本 JavaScript 对象,例如:undefined 和 true 在内存中的管理方式。 通过在编译时为这些对象分配固定的内存地址,V8 无需在运行时查找地址,从而显著提升性能。
由于引入了静态根,对常用对象的访问变得非常快,从而 V8 可以在编译时准确预测内存地址。 这种优化不仅可以加快代码执行速度,还可以提高内置 C++ 函数的性能。
3. 这些 V8 引擎更新带来了什么最近大多数 V8 更新都旨在实现相同的目的:提高性能和安全性。
正如 V8 官方网站所述,其一年内的性能提升非常大,JetStream 整体增长了 14%,Speedometer 增长了惊人的 34%。这也意味着 V8 比以往更快、更安全。 由于 WasmGC,在 Web 上运行 Python 和 Java 的可能性也非常巨大。
参考资料https://en.wikipedia.org/wiki/V8_(JavaScript_engine)
https://v8.dev/blog/sparkplug
https://blog.appsignal.com/2024/02/28/top-8-recent-v8-in-node-updates.html
https://javascript.plainenglish.io/v8-engine-and-inline-caching-in-javascript-fef80054a551
https://en.wikipedia.org/wiki/Interpreter_(computing)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toWellFormed
https://github.com/tc39/proposal-import-attributes
https://v8.dev/blog/maglev
https://www.digitalocean.com/community/tutorials/js-v8-engine