V8 是如何处理数组的?
问题
先抛出一个问题,下面两端代码哪个的效率更高?
const arr = [];
for(let i = 0; i < 10000000; i++) {
arr[i] = 1;
}
const arr = [];
arr[10000000 - 1] = 1;
for(let i = 0; i < 10000000; i++) {
arr[i] = 1;
}
答案是第一段代码的效率更高,先来看执行的耗时,运行下面的代码:
console.time('a')
const arr1 = [];
for (let i = 0; i < 10000000; i++) {
arr1[i] = 1;
}
console.timeEnd('a')
console.time('b')
const arr = [];
arr[10000000 - 1] = 1;
for (let i = 0; i < 10000000; i++) {
arr[i] = 1;
}
console.timeEnd('b')
控制台的输出如下:
可以很明显的看到执行耗时的差别,第一段代码的效率远高于第二段代码。
那么,这是为什么呢?
原因
其实是 V8 底层对数组有两种处理方式,一种是 “快速模式”(Fast Elements),另一种是 “字典模式”(Dictionary Elements),那么这两种模式分别会在什么情况下触发,以及都有什么区别呢?
快速模式
特点:快速模式是针对紧凑型数组(dense array)的优化,这种数组的特点是索引是连续的,没有“空洞”(即没有未定义的索引)。
内部处理:在快速模式下,V8 使用类似于 C++ 数组的结构来存储元素。这意味着它为数组元素分配了连续的内存空间,提高了元素访问的速度。
适用情况:当你创建的数组是紧凑的,且主要进行的是对数组元素的遍历和访问操作时,V8 会使用这种模式。
字典模式
特点:当数组变得稀疏(sparse),即数组中有大量的空洞,或者数组索引非常大时,V8 会将数组转换为字典模式。
内部处理:在字典模式下,数组被处理为一个哈希表,而不是连续的内存块。这种方式在处理大量空洞或非常大的索引时更加内存高效。
适用情况:对于具有非常高索引值或大量未定义元素的数组,或者频繁更改其大小的数组,V8 会采用这种模式。
区别
- 底层的数据结构
- 快速模式采用的是 Array
- 字典模式采用的是 HashMap
- 触发方式
- 默认是快速模式
- 当存在以下情况就会转换到字典模式
- 数组存在 “空洞”,变为稀疏数组
- 数组中的值存在多种数据类型也可能转换,例如:同时存储数字、字符串、对象等
- 数组的长度接近或超过 32 位整数的最大值
- 效率
- 快速模式的访问和遍历效率更高
- 字典模式处理稀疏数组的效率更高
应用场景
尽量使用快速模式,可以通过以下方式来处理长度过长的数组:
- 数组分片:将一个数组分为多个数组,在处理时分别处理各个子数组
- 采用 Map 或 Set 替换数组:在某些情况下 Map 或 Set 可能比稀疏数组更加高效
PS:绝大多数情况下都不太需要关注,除非这对性能的造成了很明显的影响或需要对性能进行重度优化的情况下,因为 V8 本身已经提供了很优秀的性能。
参考:https://itnext.io/v8-deep-dives-understanding-array-internals-5b17d7a28ecc
PS
作者整理了一份长达 9w+ 字数,字节、阿里、百度大佬看了都称赞的前端文档,并且还在持续更新,包含:
- HTML、CSS、JavaScript 三件套
- Vue、React 流行框架
- Webpack、Vite 构建工具
- 浏览器、安全、计算机网络、操作系统、设计模式等计算机领域基本功
- etc…
可以添加作者 Q: 1437517225 备注领取,文档可以参与评论,以及给作者留言想要的问题,欢迎大家参与共建。