v8 hidden class test
map
test1
1 | function Point(x, y) { |
./d8 --allow_natives_syntax test_hidden_class.js
1 | sakura@ubuntu:~/v8/v8/out.gn/x64.debug$ ./d8 --allow_natives_syntax test.js |
结论:相同的map值
test2
add p2.z = 14;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76sakura@ubuntu:~/v8/v8/out.gn/x64.debug$ ./d8 --allow_natives_syntax test.js
DebugPrint: 0x252f4f50c891: [JS_OBJECT_TYPE]
- map = 0x2d1249b8f8d9 [FastProperties]
- prototype = 0x252f4f50c761
- elements = 0x538c1f82241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- properties = 0x538c1f82241 <FixedArray[0]> {
#x: 10 (data field 0)
#y: 11 (data field 1)
}
0x2d1249b8f8d9: [Map]
- type: JS_OBJECT_TYPE
- instance size: 104
- inobject properties: 10
- elements kind: FAST_HOLEY_ELEMENTS
- unused property fields: 8
- enum length: invalid
- stable_map
- back pointer: 0x2d1249b8f881 <Map(FAST_HOLEY_ELEMENTS)>
- instance descriptors (own) #2: 0x252f4f50c931 <FixedArray[8]>
- layout descriptor: 0
- prototype: 0x252f4f50c761 <Object map = 0x2d1249b8f829>
- constructor: 0x3be1e3530089 <JSFunction Point (sfi = 0x3be1e352fc91)>
- code cache: 0x538c1f82241 <FixedArray[0]>
- dependent code: 0x538c1f82241 <FixedArray[0]>
- construction counter: 6
DebugPrint: 0x252f4f50c981: [JS_OBJECT_TYPE]
- map = 0x2d1249b8f8d9 [FastProperties]
- prototype = 0x252f4f50c761
- elements = 0x538c1f82241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- properties = 0x538c1f82241 <FixedArray[0]> {
#x: 12 (data field 0)
#y: 13 (data field 1)
}
0x2d1249b8f8d9: [Map]
- type: JS_OBJECT_TYPE
- instance size: 104
- inobject properties: 10
- elements kind: FAST_HOLEY_ELEMENTS
- unused property fields: 8
- enum length: invalid
- stable_map
- back pointer: 0x2d1249b8f881 <Map(FAST_HOLEY_ELEMENTS)>
- instance descriptors (own) #2: 0x252f4f50c931 <FixedArray[8]>
- layout descriptor: 0
- prototype: 0x252f4f50c761 <Object map = 0x2d1249b8f931>
- constructor: 0x3be1e3530089 <JSFunction Point (sfi = 0x3be1e352fc91)>
- code cache: 0x538c1f82241 <FixedArray[0]>
- dependent code: 0x538c1f82241 <FixedArray[0]>
- construction counter: 6
DebugPrint: 0x252f4f50c981: [JS_OBJECT_TYPE]
- map = 0x2d1249b8f989 [FastProperties]
- prototype = 0x252f4f50c761
- elements = 0x538c1f82241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- properties = 0x538c1f82241 <FixedArray[0]> {
#x: 12 (data field 0)
#y: 13 (data field 1)
#z: 14 (data field 2)
}
0x2d1249b8f989: [Map]
- type: JS_OBJECT_TYPE
- instance size: 104
- inobject properties: 10
- elements kind: FAST_HOLEY_ELEMENTS
- unused property fields: 7
- enum length: invalid
- stable_map
- back pointer: 0x2d1249b8f8d9 <Map(FAST_HOLEY_ELEMENTS)>
- instance descriptors (own) #3: 0x252f4f50ca51 <FixedArray[11]>
- layout descriptor: 0
- prototype: 0x252f4f50c761 <Object map = 0x2d1249b8f931>
- constructor: 0x3be1e3530089 <JSFunction Point (sfi = 0x3be1e352fc91)>
- code cache: 0x538c1f82241 <FixedArray[0]>
- dependent code: 0x538c1f82241 <FixedArray[0]>
- construction counter: 6
结论:指向一个新的map
property的管理方法
In-Object Properties
1 | function Point(x, y) { |
V8支持所谓的对象内属性,它们被直接保存在对象上,和对象在同一块内存区域。这种属性是V8中速度最快的属性,能够被直接访问。对象内属性的个数由对象初始化大小决定。如果要添加的属性超出对象的大小,这些属性就会被放入properties store中。properties store会增加一层属性访问的消耗,但是不受大小的限制。
Fast Properties
一般来说,我们会把属性保存在线性的properties store中,作为快速属性。快速属性可以通过简单的索引在properties store中访问。不过为了从属性名从properties store获取实际位置,我们要去HiddenClass上的 descriptor array中查询 。具体来讲,这一步骤是先根据属性名搜寻隐藏类的descriptor array,然后得到到数组的偏移地址,然后根据偏移地址到properties store上读取属性信息。实际上,第一次查找属性的时候难免会经历一次隐藏类的哈希查找,但下次一般会结合内联缓存,直接采用缓存的位移来存取属性。
Slow Properties
然而,若是有很多属性从对象上添加或者删除,就需要花费很多时间和内存来维护descriptor array以及隐藏类。因此,V8也支持所谓的慢属性。拥有慢属性的对象会拥有一个自给的字典作为它的properties store(如上图所示)。这种情况下,所有属性的元信息不再储存在隐藏类的descriptor array中,而是直接保存在属性字典上。因此,此时属性的添加和移除不用再去更新指向的隐藏类。因为内联缓存不适用保存在字典中的属性,所以慢属性一般要比快属性慢。
总结
列表内容有三种不同类型的命名属性:对象内属性,快属性,以及慢/字典属性。
1. 对象内属性直接储存在对象自身上,提供最快的属性访问。
2. 快属性活跃在properties store上,所有相关的元信息都在隐藏类的descriptor array中。
3. 慢属性保存在一个自给的属性字典上,属性的元信息不再与隐藏类有关。
慢属性提供高效的属性添加删除,但是访问速度要慢于快速属性和对象内对象。
Elements or array-indexed Properties
目前为止我们介绍了命名属性并且忽略了通常出现在数组中的整数索引属性。处理这种整数型属性要比命名属性简单很多。
所有的整数型属性都被单独保存在一个elements store中,并且有多达20种元素类型。
Packed or Holey Elements
1 | const o = ["a", "b", "c"]; |
1 | parallels@ubuntu:~/v8/v8/out.gn/x64.debug$ ./d8 --allow_natives_syntax test.js |
简言之,如果属性在接收者上找不到(接受者可以理解为要访问的属性或者方法指向的那个对象),那么就会继续到原型链上找。
这些元素是自给自足的,换言之,我们不需要在隐藏类上储存这些索引属性。另外,我们需要一个特殊的值,称之为空洞,来标记那些不存在属性。这点对于数组方法的性能很关键。
若是我们知道elements store没有空洞,是被填满的,我们可以提高本地操作(指无需原型链参与)的性能,不需要再去花费昂贵的代价查找原型链。
Fast or Dictionary Elements
第二个关于元素的主要的区分为是否是快速或是字典模式。快速元素就是VM内部简单的将数组索引与elements store中的索引映射。
然而,这种简单表示,在那种有很大的空洞以及很少位置被占用的数组上是相当浪费的。在这种情况下,我们将转换成字典模式,这会减小内存但轻微的牺牲性能。
1 | const sparseArray = []; |
1 | gdb-peda$ r --allow_natives_syntax test.js |
0x270F=9999
在这个例子中,如果给这个数组分配完整的10k个空间将会造成极大浪费。而实际上V8是创建了一个key-value-descriptor的triplets。这个例子中的key是“9999”,value是“foo”,而descriptor是使用的默认值。
1 | const array = []; |
1 | gdb-peda$ r --allow_natives_syntax test.js |
在上面例子中,我们给数组添加了一个non-configurable属性。这个信息就会被保存慢元素字典的triplet中的descriptor 部分。需要切记一点,数组方法在那些拥有慢元素的对象上的性能会相当慢。
The ElementsAccessor
你可以想到开发人员根本不愿意为这20种元素在C++中对应重复写20遍数组方法。这里就是体现C++神奇的地方了。为了不实现数组方法一遍又一遍,我们建立了一个属性存取器(ElementsAccessor),在它里面大部分都仅仅是简单的属性访问相关的方法。这个属性访问其依赖C++中的CRTP来实现不同种类需求的数组方法。所以,有时如果我们调用例如数组的slice,V8会调用C++中的代码,然后通过属性存取器来选择所需函数,如slice的专门版本。
学习v8字节码
阅读和打印(只是个例子)
./d8 --print-bytecode test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function incrementX(obj) {
return 1 + obj.x;
}
incrementX({x: 42});
....
....
$ node --print-bytecode incrementX.js
...
[generating bytecode for function: incrementX]
Parameter count 2
Frame size 8
12 E> 0x2ddf8802cf6e @ StackCheck
19 S> 0x2ddf8802cf6f @ LdaSmi [1]
0x2ddf8802cf71 @ Star r0
34 E> 0x2ddf8802cf73 @ LdaNamedProperty a0, [0], [4]
28 E> 0x2ddf8802cf77 @ Add r0, [6]
36 S> 0x2ddf8802cf7a @ Return
Constant pool (size = 1)
0x2ddf8802cf21: [FixedArray] in OldSpace
- map = 0x2ddfb2d02309 <Map(HOLEY_ELEMENTS)>
- length: 1
0: 0x2ddf8db91611 <String[1]: x>
Handler Table (size = 16)
- LdaSmi [1]
LdaSmi [1]将常量 1 加载到累加器中。 - Star r0
接下来,Star r0 将当前在累加器中的值 1 存储在寄存器 r0 中。 - LdaNamedProperty a0, [0], [4]
LdaNamedProperty 将 a0 的命名属性加载到累加器中。ai 指向 incrementX() 的第 i 个参数。在这个例子中,我们在 a0 上查找一个命名属性,这是 incrementX() 的第一个参数。该属性名由常量 0 确定。LdaNamedProperty 使用 0 在单独的表中查找名称:1
2- length: 1
0: 0x2ddf8db91611 <String[1]: x>
可以看到,0 映射到了 x。因此这行字节码的意思是加载 obj.x。
那么值为 4 的操作数是干什么的呢? 它是函数 incrementX() 的反馈向量的索引。反馈向量包含用于性能优化的 runtime 信息。
现在寄存器看起来是这样的:
Add r0, [6]
最后一条指令将 r0 加到累加器,结果是 43。 6 是反馈向量的另一个索引。Return
Return 返回累加器中的值。返回语句是函数 incrementX() 的结束。此时 incrementX() 的调用者可以在累加器中获得值 43,并可以进一步处理此值。
乍一看,V8 的字节码看起来非常奇怪,特别是当我们打印出所有的额外信息。但是一旦你知道 Ignition 是一个带有累加器寄存器的寄存器,你就可以分析出大多数字节码都干了什么。
bytecode如何产生
v8自带的gdb命令
在/tools中可以找到gdbinit和gdb-v8-support.py
我将其都复制到调试路径下1
2
3parallels@ubuntu:~/v8/v8/out.gn/x64.debug$ ls | grep gdb
gdbinit
gdb-v8-support.py
对如下代码进行测试1
2var a=[0xdeadbee,0xdeadbeef,"hoge"];
while(1);
1 | gdb-peda$ source gdbinit |
JIT
print flag
1 | parallels@ubuntu:~/v8/v8/out.gn/x64.debug$ ./d8 --help |grep print |
IR可视化工具:turbolizer
安装
1 | sakura@sakuradeMacBook-Pro:~/Desktop/v8/tools/turbolizer$ npm i |
使用
生成的在build目录下,如图
然后在turbolizer目录下启动服务1
python -m SimpleHTTPServer 8000
打开Chrome浏览器,注意一件事情,那就是这东西十分不好用……他对浏览器兼容适配很不好,Chrome勉强能用,提示无效的json文件就刷新几下看看……
选择输入文件。
这里的输入文件是来源于–trace-turbo这个flag生成的json文件。
我举个例子
1 | function add(a,b) { return a + b; } |
1 | ./d8 --allow-natives-syntax --trace-turbo test.js --trace-turbo-path /Users/sakura/Desktop/tur |
–allow-natives-syntax是用来开启%OptimizeFunctionOnNextCall(add)标志的
–trace-turbo是trace TurboFan优化阶段,生成json文件
–trace-turbo-path 指定输出json的目录
然后上传上去就可以选择阶段和可视化的看IR了~
因为这个工具的一些兼容关系,你可能需要自己改一下html,比如我就是height太小(原本25px,逗我……)