从V8来看数组动态扩容以及对数组的优化

V8对数组的优化

在 c++、java 中的数组的特点是:是通过在内存中划分一串连续的、固定长度的空间。来存放一组有限的并且是相同类型的数据结构。

js中的数组

var arr = [100, 12.3, 'a', function () {return 1}, {a: 1}];
arr[arr.length] = '12334';
arr.length = 1;

js 中的数组可以存放任意的类型。 可以动态的来给数组修改长度。

  1. 支持任意的类型。
  2. js数组可以动态的的改变容量,根据数组的数据来扩容、收缩。
  3. js提供了很多的操作数组的方法。
  4. js数组不是基础的数据结构实现的,而是在基础上做了一些封装。

V8中看数组

JSArray 继承自 JSObject, 数组是一个特殊的对象。

本质上数组也是一个对象,内部也是 key-value 的存储形式。

底层是一个 Map,key 为0,1,2,3索引。 index 为字符串。

数组有两种表现形式, fast和slow。

fast(快数组 FAST ELEMENTS)

快数组是一种线性的存储方式。新创建的空数组,默认的存储方式是快数组,快数组长度是可变的,可以根据元素的增加和删除来动态调整存储空间大小,内部是通过扩容和收缩机制实现。

  1. 扩容
    新容量的计算方式: 扩容后的新容量 = 旧容量 * 1.5 + 16;

扩容后会将数组拷贝到新的内存空间中。

  1. 扩容
var arr = [1, 2];

// 扩容后的新容量 = 旧容量 * 1.5 + 16;

arr[arr.length] = 3;
/*
新容量 = 2 * 1.5 + 16;
3 + 16 = 19;
*/
  1. 收缩

如果容量 > length * 2 + 16, 旧进行收缩容量。 否则就调用 holes 对象来填充未被初始化的位置。

收缩的大小:

int elements_to_trim = length + 1 == old_length ? ... : ...;

根据 length +1 和 old_length 来判断,是将空出的内容全部收缩还是只是收缩一半。

holes(空洞) 对象指的是数组中分配了空间,但是如果没有存放元素的位置。这个模式叫 Fast Holey Element 模式。

  1. 收缩
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
arr.length = 1;

// 如果 容量 > length * 2 + 16
// 否则 用 holes 对象填充未被初始化的位置

/*
20 > 1 * 2 + 16  
将要进行收缩
*/
// 那么是将空出的空间全部收缩还是收缩二分之一

length + 1 == old_length ? 空出空间 / 2: 空出空间;

1 + 1 == 20 ? 空出空间/2 : 空出空间。

所以: 这次操作会将空出来的空间全部收回。

目前容量 = 1;

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
arr.length = 2;
14 > 2 * 2 + 16 不成立
使用 holes 对象填充位被初始化的位置
最终容量展示: [1, 2, empty * 12];
  1. 新建数组时,如果没有设置容量,V8会默认使用 Fast Elements 模式实现。 如果要对数组设置容量,但是没有进行内部元素的初始化, new Array(10) 数组就存在了空洞,就会以 Fast Holey Elements 模式实现。

slow(慢数组)

慢数组是一种字典内存形式。不用开辟大的连续的内存空间,节省空间。但是需要维护一个 HashTable。效率比快数组低。

hasTable: 散列表,是根据 key 来直接访问在内存存储位置的数据结构。通过计算一个关于键值的函数,将所需要查询的数据映射到表中一个位置来访问记录,加快了查找速度。

fast 和 slot 区别

  1. 存储方式方面:快数组内存中连续的,慢数组在内存中零散分配。

  2. 内存使用方面:快数组内存是连续的,需要开辟一块大的内存供使用,可能会浪费较多的内存空间。慢数组不会有空洞的情况, 都是零散的内存,比较节省内存空间。

  3. 遍历效率:快数组由于空间连续,遍历速度快。慢数组每次都要寻找 key 的位置,遍历效率会差一点。

  4. 数组的存储结构是有变化的,会根据不同的情况将快慢数组转换。

  5. 快速组/慢数组

console.time('time');
const arr = new Array(1030);
console.log(arr[1029]);
console.timeEnd('time');


console.time('time2');
var a = [];
for (var i = 0; i < 1030; i++) {
    a.push(i);
}
console.log(a[1029]);
console.timeEnd('time2');

// 结果
undefined
time: 6.866ms
1029
time2: 0.607ms

快->慢

新容量 >= 3 * 扩容后的容量 * 2 ,会转为慢数组。

至少有 1024 个空洞,会转变为慢数组。 这个时候对数组分配大量空间可能造成存储空间的浪费,为了空间的优化,会转为慢数组。

  1. 快数组转为慢数组
console.time('time3');
let a = [1, 2];
a[1030] = 1;
console.log(a[1029]);
console.timeEnd('time3');

慢->快

处于散列表实现的数组,在每次空间增长时,V8的启发算法会检查其空间占用量,若其空洞口减少到一定程度,就会转为快数组模式。

当慢数组的元素可存放在快数组中且长度在 smi 之间且仅节省了50%的空间,就会转为快数组。

  1. 慢数组转为快数组
console.time('time');
const arr = new Array(1030);
for (var i = 200; i < 1030; i++) {
    arr[i] = i;
}
console.log(arr[1029]);
console.timeEnd('time');

// 结构
time: 5.918ms

我们可以看出来,即使慢数组变为快数组, 效率也不一定高。

测试一(测试扩容/收缩容量)

  1. 扩容
var arr = [1, 2];

// 扩容后的新容量 = 旧容量 * 1.5 + 16;

arr[arr.length] = 3;
/*
新容量 = 2 * 1.5 + 16;
3 + 16 = 19;
*/
  1. 收缩
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
arr.length = 1;

// 如果 容量 > length * 2 + 16
// 否则 用 holes 对象填充未被初始化的位置

/*
20 > 1 * 2 + 16  
将要进行收缩
*/
// 那么是将空出的空间全部收缩还是收缩二分之一

length + 1 == old_length ? 空出空间 / 2: 空出空间;

1 + 1 == 20 ? 空出空间/2 : 空出空间。

所以: 这次操作会将空出来的空间全部收回。

目前容量 = 1;

模式: Fast Elements

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
arr.length = 2;
14 > 2 * 2 + 16 不成立
使用 holes 对象填充位被初始化的位置
最终容量展示: [1, 2, empty * 12];

模式:Fast Holey Elements

测试二(测试快/慢数组,以及快慢数组转换)

  1. 快速组/慢数组
console.time('time');
const arr = new Array(1030);
console.log(arr[1029]);
console.timeEnd('time');


console.time('time2');
var a = [];
for (var i = 0; i < 1030; i++) {
    a.push(i);
}
console.log(a[1029]);
console.timeEnd('time2');

// 结果
undefined
time: 6.866ms
1029
time2: 0.607ms
  1. 快数组转为慢数组
console.time('time3');
let a = [1, 2];
a[1030] = 1;
console.log(a[1029]);
console.timeEnd('time3');
  1. 慢数组转为快数组
console.time('time');
const arr = new Array(1030);
for (var i = 200; i < 1030; i++) {
    arr[i] = i;
}
console.log(arr[1029]);
console.timeEnd('time');

// 结构
time: 5.918ms

我们可以看出来,即使慢数组变为快数组, 效率也不一定高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值