注意数组 - V8 引擎建议

2025-06-08

注意数组 - V8 引擎建议

在这篇博客中,我决定讨论数组及其在 V8 中的行为。通过理解这些,你可以编写出高效的代码,并利于 V8 的优化。

在 V8(Google Chrome 和 Node.js 背后的 JavaScript 引擎)中,“元素类型”是指用于优化数组操作的内部分类。V8 使用这些分类来假设数组包含的元素类型,从而优化对这些数组的访问和操作。对于希望编写高性能 JavaScript 代码的开发者来说,了解元素类型至关重要,因为某些操作可能会导致数组在不同类型之间转换,从而可能影响性能。

在运行 JavaScript 代码时,V8 会跟踪每个数组包含的元素类型。这些信息使 V8 能够针对特定类型的元素优化对数组的任何操作。例如,当您对数组调用reducemap或 时forEach,V8 可以根据数组包含的元素类型来优化这些操作。

V8 根据数组存储值的类型以及是否存在“空洞”(缺失元素),将数组分为不同的“元素类型”。这种分类使得 V8 能够根据数组的内容,使用更高效的存储和访问方法。以下是每种类型的详细介绍:

拥挤与空洞

打包元素:这些数组在第一个和最后一个定义位置之间没有缺失元素。访问打包数组通常更快,因为 V8 可以优化内存布局和访问模式,例如假设一个连续的元素块:const array = [1, 2, 3];

空洞元素:这类数组包含空洞,即未定义的位置,如果删除元素或声明的数组初始大小大于其包含的元素数量,则可能会出现这种情况。对空洞数组的操作通常较慢,因为 V8 必须在访问元素之前检查元素是否存在。例如,如果您编写类似以下示例的代码,V8 会将此数组视为空洞元素:

图表

元素类型

在压缩和多孔分类中,数组根据其存储的元素类型进一步分类:

Smi 元素: “Smi”代表“小整数”,指的是一种特殊的优化方法,用于将 31 位整数直接存储在指针中,从而节省空间和访问时间。仅包含 Smi 值的数组与包含其他类型值的数组的优化方式不同。

Smi元素

双精度元素:这些数组存储浮点数。由于浮点数的存储格式与整数或其他类型不同,V8 对仅包含双精度值的数组进行了不同的优化。

双元件

元素:此类别指的是可以包含任意类型元素的数组,除了数字之外,还包括对象、字符串和符号。这些数组在可包含的值类型方面最为灵活,但可能无法像更专业的数组那样受益于某些优化。

元素

元素种类格

V8 将这个标签转换系统实现为一个格子结构。以下是一个简单的可视化示例,仅展示了最常见的元素类型:

元素种类格

格子结构只能向下转换。一旦将一个浮点数添加到 Smi 数组中,它就会被标记为 DOUBLE,即使你之后用 Smi 覆盖了该浮点数。同样,一旦数组中出现了一个洞,它就会被永远标记为有洞,即使你之后把它填满。

一般来说,元素类型越具体,优化的粒度就越细。元素类型在格中的位置越低,对该对象的操作速度可能就越慢。为了获得最佳性能,请避免不必要地转换为不太具体的类型——坚持使用适合您情况的最具体的类型。

性能提示

大多数情况下,元素类型追踪在后台运行,不可见,您无需担心。但您可以采取以下措施,最大限度地利用该系统。

  • 避免读取超出数组长度的内容

for-of如今,和的性能forEach与老式的 for 循环相当,并且当您循环的集合是可迭代的时,只需使用for-of和,特别是对于数组,您可以使用forEach内置的。

  • 避免元素类型转换

一般来说,如果您需要对数组执行大量操作,请尝试坚持尽可能具体的元素类型,以便 V8 可以尽可能优化这些操作,例如,只需将 1.1 添加到小整数数组中就足以将其转换为 PACKED_DOUBLE_ELEMENTS。

避免元素类型转换

NaN和 也是同样的道理Infinity。它们表示为双精度数,因此将单个NaN或添加Infinity到 SMI_ELEMENTS 数组会将其转换为 DOUBLE_ELEMENTS。

  • 避免多态性

如果您有处理多种不同元素类型的数组的代码,则它可能导致多态操作,其速度比仅对单一元素类型进行操作的代码版本更慢。

请考虑以下示例,其中调用了一个包含各种元素类型的库函数。(请注意,这不是原生的 Array.prototype.forEach)

避免多态性

内置方法(例如Array.prototype.forEach)可以更有效地处理这种多态性,因此在性能敏感的情况下可以考虑使用它们而不是用户库函数。

  • 避免产生孔洞

这是你必须考虑的最重要的技巧之一,因为一旦数组被标记为有孔,它就永远是有孔的——即使它的所有元素后来都存在了!
当数组中缺少元素时,就会出现有孔数组,由于引擎需要处理潜在的“孔”,导致访问和操作效率降低。以下是避免创建有孔数组的方法:

创建孔

如果您预先知道数组的大小,但用空位初始化它,它将变成一个有洞的数组。相反,应该使用已知值预先初始化它。对于整数,您可以使用 0 或其他占位符值。为了更简洁地初始化数组而不产生空洞,请使用 fill 方法。当数组大小已知但想要避免空洞时,这种方法尤其有用。

充满

此外,从数组中删除元素可能会引入空洞,使其变成一个有空洞的数组。如果需要删除某个元素,请考虑将其设置为 ,undefined或者null如果您无法使用诸如 之类的方法splice在不留下空隙的情况下重构数组。

拼接

对于最后一个向数组添加新元素时,请使用push或之类的方法,unshift而不是直接通过索引设置它们,如果索引超出当前数组长度,则可能会产生漏洞。

推移-取消移位

在编写性能敏感的 JavaScript 代码时,了解这些数组类型如何影响性能可以指导您如何构建数据。尽可能保持数组同质(全部为 Smi 或全部为双精度浮点型),可以利用 V8 的优化功能实现更快的访问和操作。

有趣的部分:))

对于调试元素类型以确定给定对象的“元素类型”,请获取 v8 的调试版本(通过在调试模式下从源代码构建或通过使用jsvu获取预编译的二进制文件),然后运行:
out/x64.debug/d8 --allow-natives-syntax
请注意,“COW”代表写时复制,这是另一项内部优化。:))

调试

最后,希望您喜欢这个博客并了解有关 v8 及其背后的 javascript 魔力的新知识。

鏂囩珷鏉ユ簮锛�https://dev.to/alirezaebrahimkhani/be-careful-about-arrays-v8-engine-advice-1pmk
PREV
2018 年你应该学习函数式编程
NEXT
使用 Go 和 TDD 构建财务跟踪 REST API - 第 1 部分