图解 Google V8 # 03:快属性和慢属性:V8是怎样提升对象属性访问速度的?
Posted 凯小默
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图解 Google V8 # 03:快属性和慢属性:V8是怎样提升对象属性访问速度的?相关的知识,希望对你有一定的参考价值。
说明
图解 Google V8 学习笔记
线性结构和非线性结构
javascript 中的对象是由一组组属性和值的集合,就像一个字典,字符串作为键名,任意对象可以作为键值,可以通过键名读写键值。
而在 V8 实现对象存储时,并没有完全采用字典的存储方式,而是用的非线性的数据结构,查询效率会低于线性的数据结构,V8 为了提升存储和查找效率,采用了一套复杂的存储策略。下面看看是用了什么策略去提升了对象属性的访问速度?
常规属性 (properties) 和排序属性 (element)
我们先看一个例子:
function Foo()
this[100] = 'test-100'
this[1] = 'test-1'
this["B"] = 'bar-B'
this[50] = 'test-50'
this[9] = 'test-9'
this[8] = 'test-8'
this[3] = 'test-3'
this[5] = 'test-5'
this["A"] = 'bar-A'
this["C"] = 'bar-C'
var bar = new Foo()
for(key in bar)
console.log(`index:$key value:$bar[key]`)
我们无序的给函数添加属性然后打印出来,结果如下:
我们发现:
- 设置的数字属性被最先并且按照数字大小的顺序打印出来;
- 设置的字符串属性依然是按照之前的设置顺序打印的。
总的来说就是:一个对象中如果有数字型属性和非数字型属性,当遍历对象并打印出该对象的所有属性时,会优先把数字型属性按升序排序后打印,接着再对非数字型属性按添加的先后顺序打印出来。
原因:在 ECMAScript 规范中定义了数字属性应该按照索引值大小升序排列,字符串属性根据创建时的顺序排列。
- 对象中的数字属性称为排序属性,在 V8 中被称为
elements
- 字符串属性就被称为常规属性,在 V8 中被称为
properties
。
在 V8 内部,分别使用了两个线性数据结构来分别保存排序属性和常规属性,从而能有效地提升存储和访问这两种属性的性能,如图所示:
如果执行索引操作,那么 V8 会先从 elements 属性
中按照顺序读取所有的元素,然后再在 properties 属性
中读取所有的元素。
快属性和慢属性
对象内属性
将不同的属性分别保存到 elements 属性和 properties 属性中,简化了程序的复杂度,但是在查找元素时,却多了一步操作,会影响到元素的查找效率。
比如:上面执行 bar.B这个语句来查找 B 的属性值,那么在 V8 会先查找出 properties 属性所指向的对象 properties,然后再在 properties 对象中查找 B 属性
基于这个原因,V8 采取了一个权衡的策略:将部分常规属性直接存储到对象本身去加快查找属性的效率,我们把这称为对象内属性 (in-object properties)。
比如:现在要执行 bar.B这个语句来查找 B 的属性值,那么 V8 就可以直接从 bar 对象本身去获取该值了。
对象内属性是默认10,超出部分会被存放在常规属性存储中保存,可以自由扩容。
快属性
通常,我们将保存在线性数据结构中的属性称之为快属性。
因为线性数据结构中只需要通过索引即可以访问到属性,虽然访问线性结构的速度快,但是如果从线性结构中添加或者删除大量的属性时,则执行效率会非常低,这主要因为会产生大量时间和内存开销。
慢属性
如果一个对象的属性过多时,V8 就会采取另外一种存储策略,那就是慢属性策略,但慢属性的对象内部会有独立的非线性数据结构 (词典) 作为属性存储容器。所有的属性元信息不再是线性存储的,而是直接保存在属性字典中。
开启慢速模式时,使用hash作为底层存储结构,key为字符串,字面量会发生类型转换。
数组索引属性和命名属性存储在两个单独的数据结构中:
慢属性是如何存储的:
慢属性使⽤ HashMap 作为属性存储,⽽是直接存储在属性 Hash 中(没有缓存,所以叫慢属性)。
总结
参考资料
以上是关于图解 Google V8 # 03:快属性和慢属性:V8是怎样提升对象属性访问速度的?的主要内容,如果未能解决你的问题,请参考以下文章
图解 Google V8 # 06:原型链:V8是如何实现对象继承的?
图解Google V8,搞懂 JavaScript 执行逻辑