由貘大编写,我负责搬砖
不知道出于什么不可告人的原因 @裕波 要让偶写 V8 引擎实现的 Hidden Class 是怎么回事[lt雷]。可偶是个懒人啊,偶也不会这个啊,肿麽办[熊猫]?!他不是想让偶难堪吧,Hidden Class 这种能跟 EventLoop/AIO 比肩的操作系统深层次原理的东西(你们懂的 [doge] ),偶咋会知道呢,哭[泪]。
好在偶还会 Google 啥的,随便查查,然后拼凑出点内容忽悠过周老板就好了(反正他也不懂 [阴险])。
开始絮叨。
V8 Hidden Class 这东西吧貌似很早就有了,一种引擎实现机制,不知道为啥周董会对这个感兴趣,起码 2009 年时候 Google 就说过这个么[挖鼻屎]。
这玩意偶觉得是对 web 前端没啥用,当然你说对写千万负载的 Node 服务兴许有用,那偶也不反对啦。实际上它就是个类属性收集器(方式)而已,避免每次查找对象属性的 hash 操作。
大概看看 Java 例子(当然这些都是偶Google抄来的[得意地笑])
class Point {
Point(int x, int y) {
this.x = x;
this.y = y;
}
private:
int x;
int y;
}
Point p1 = new Point(10, 11);
Point p2 = new Point(12, 13);
Point 类的两个实例存放方式:
好了,这是Java,它可以共享 Class info ,很好理解因为 Java 不是动态脚本,运行时不能为类对象添加属性。它的 Class info 可以在制定内存地址保存固话。成员对象属性先访问到info 表,获取得到属性对应值地址偏移后,通过指针偏移得到属性值。
回过头来看 JS 这货。
它的对象压根就是个哈希表(大字典……)着存呗,存上key和value完事,key被hash,value是值地址。
比如类似 Java 的代码
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
大概是这样:
发现没,Point 的成员属性都是相同的,但是被分散存储了。每个对象取值都要 hash key 然后再找到其 value。
Google 这帮货不爽了就,他们觉得 hash 太慢,要变变,起码变成跟 Java 内样的。
为了这个蛋疼[喵喵]目的于是 JS Object 就变成了这样一组结构:
每个 WORD 是一个32位指针,4字节对齐(4*8 = 32)
地址的最低两位可以用做标记,这样31位带符号整数可以区分标记和指针
(这个其实跟现在说的没啥关系,主要是为了区分密实数组) (V8 对于0开始连续索引数,且元素<64k的采用密实数组形式保存,就是真数组,如果delete某index就会转为散列形式——你懂的,又“慢”呗) 一个 Object 由三个 WORD 构成,分别记录
有了这个结构,为的就是把有相同 Properites 的对象指到同一个 Class info 数据结构上去。这样就免去每次key时的 hash 了,就如同 Java 一般。
看代码执行过程:
function Point(x, y) {
this.x = x;
this.y = y;
}
--> var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
先建立了一个 Point 构造函数对应的 Hidden Class —— Class 0。
然后 p1 对象的 Hidden 指针指向它:
function Point(x, y) {
--> this.x = x;
this.y = y;
}
var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
this.x 赋值时,Class 0 里添加 x 属性指针指向新建的 Class 1 结构,其内存储 x 属性在 Object 内 Properties 指针所指存储地址偏移值,这里假定为 0 好了。根据这个偏移地址,p1 Object 在属性值地址的偏移0位保存 x 的值 10。 (这里的offset仅是示意,用0、1、2、3……表示,实际上根据值类型不同,offset 会更大。比如,例子中的 10 假设为会存储为 int32,那么下次 offset 会到 32。后面的例子子里仅仅用 1 表示已经偏移了)
function Point(x, y) {
this.x = x;
--> this.y = y;
}
var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
this.y 赋值时,新建 Class2 ,保留 Class1 值,在Class 1并添加 y 属性指针指向新建的 Class 2 结构,Class 2 开辟空间储 y 属性在 Object 内 Properties 指针所指存储地址偏移值1。 由于这次 new 已经结束,将 Hidden 指针指向最终的 Class 2。
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(10, 11);
-> var p2 = new Point(12, 13);
继续执行到 p2 对象的构建。它是 Point 的实例,Point 本身的 info 已经在 p1 实例化时构建完成。因此仅仅执行一边 Class 0 - 2 的链接指向存放属性值就可以。
先指向 Class 0,知道有属性 x 要添加。
function Point(x, y) {
--> this.x = x;
this.y = y;
}
var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
然后指向 Class 1,在指定offset 0 处添加值 12。
function Point(x, y) {
this.x = x;
--> this.y = y;
}
var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
继续指向 Class 2 添加值 y,并固定 Hidden Class 指针指向 Class 2。
最终获得与 Java 类似的对象数据存储结构,使用指针偏移替代了key 的 hash 计算。
到此,基本上说完 Hidden Class 是什么,怎么整的。
然后呢然后呢然后呢然后呢,这种优化肯定有问题吧[挖鼻屎]。如果构造对象的属性不变更,那么 Hidden Class 机制肯定是快的。但是——JS 对象是可以动态添加移除属性的不是么。 如果是这样的代码,肿么办?
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
// →_→
p2.z = 14;
这样 Class 2 得 Add z。
之后再来个 Class 3 ……
此时由于 p2 属性修改,导致 p1 与 p2 分属两个 Hidden Class 来控制器属性值偏移。
这只是修改的情况,如果删除了某属性值呢?
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(10, 11);
var p2 = new Point(12, 13);
// →_→
delete p2.x;
那好,玩不了,老实把所有属性值遍历,乖乖转为哈希表存。这样 p1 是通过指针偏移访问的,p2 就是相对慢的哈希了。
说这么多,其实还有很多情况让V8没法好好使用 Hidden Class 模式构建对象。 比如:
function Point(x, y, z) {
if (z) {
this.x = x;
} else {
this.y = y;
}
}
function Point(x, y, z) {
if (z) {
this.y = x;
} else {
this.x = y;
}
}
类似运行时属性赋值顺序不同,运行时属性缺失等情况都将击破这种优化,退回到哈希表模式。
这还没完,如果你细心,就会发现 Hidden Class 初始时候是针对构造函数的。例子里用的时 Point 函数。实际情况下 {} 字面量和实际上是 new Object(),对 Point 实例构造的 Hidden Class 过程等同于对 Objcet 实例构造过程。所以还存在一种比较容易碰到的优化变拖累情况。
var t = new Date();
var max = 1e4;
for(var i = 0; i < max; ++i) {
var n = {};
n['i_' + i] = i;
}
console.log('首次', new Date-t, '===== n =====');
t = new Date();
for(var i = 0; i < max; ++i) {
var m = {};
m['i_' + i] = i;
}
console.log('重复', new Date-t, '===== m =====');
Chrome 结果可能是(根据机器速度不同数值可能有浮动):
而 Safari 这样没有采用此优化方式的结果可能是:
好了,叨逼这么多,基本上算是说明白了吧[lt蛋疼]。结贴,跟周董要吃喝去[bm内涵]
扫码关注w3ctech微信公众号
确实,这样子偏移查询就很快了,可能是顺序表结构的原因查询忒快,但是牺牲的就是改动属性的时候没有hash这种类似链表结构方式的快。不知道理解错没,按代码执行顺序讲解太好啦~
不过java属性赋值有下面这个写法么o好怪... private: int x; int y;
不好意思,偶Qt写多了,哇哈哈哈。
** 貘大的必须挺啊
请问v8中对应的代码在哪里啊?谢谢。
共收到4条回复