众所周知,V8采用了很多先进的思想和技巧来加速JavaScript的执行。其中重要的一点就是关于对象的表示。本文只介绍关于对象的表示,所以会隐去一些过程和相关细节。
对象的结构
v8会把对象看作一个单独的类,像Java中的类那样,也就是隐藏类,它在V8的内部就是一个Map结构。
当对象每新增一个的属性都会生成为一个新的Map。这个Map描述了一个对象在内存中的结构。 通过这个Map结构V8实现了快速的属性访问。
例如,初始声明了一个对象o,里面包含属性x。这时候V8为对象o生成一个Map M。这个Map里面包含了一个属性x,以及x的偏移地址等信息。
当执行到下一个属性赋值语句的时候,因为新增了一个属性,对象的结构发生了改变,所以又会生成一个新的Map N。
这个新的Map N相比 Map M会增加一条记录,就是刚才添加的属性y。除此之外,Map M也会发生改变,里面会添加一条能指向Map N的pointer,这个指向告诉了引擎如果你想查找y,请到Map N去。若是后面o再发生改变,新增属性z,那么除了再生成新的Map,又会有从Map N指向新Map的pointer。
所以,若是在一个大型项目中,根据上面的规则就会生成很多个Map,它们会连接在一起,看起来像这样
对象的访问
了解了对象再V8中的机构,我们接下来看看一个对象使用的实际例子。
看下面几个对象。
a = {x:1,d:3}
{x:1,c:4}
{x:1,b:5}
{x:1,d:5}
我们再声明一个函数,对它们进行操作
function load(o){
return o && o.x;
}
首先,我们只用对象a,V8会为对象a创建一个Map,包含里面属性的偏移地址等。把对象a作为参数传入load运行后,就会把这个Map缓存在这个函数信息里面。当缓存了这个Map后,下次再传入另外一个对象,就会发生下面的情况
if (vector[slot].state == MONO) {
if (a.map == vector[slot].map) {
return valueAtOffset(a, vector[slot].offset);
} else {
}
} else {
..
}
这里需要说明的是当一个call site只会接受一种类型,此时称为Monomorphic,类型数目在2-4个的时候,称为Polymorphic。一旦接受的类型超过了4个,称为Megamorphic,它们的关系如下。
当我们只传入了一个类型a时,属于单态Monomorphic。后面运行会检查传入的参数类型是否和之前生成的Map相一致,如果一致,是和对象a一样的结构。就直接通过Map中保存的偏移地址访问属性。
接下来,将这几个对象类型都传入函数load中,在经过多次运行后,V8根据之前解释器生成的字节码运行得出的反馈信息,V8为这四个对象生成的四个Map就会作为函数的四种输入类型,编译器会假设以后load函数传入的对象结构也会是这四种之一。
然后,下次再传入参数,就会把传入的参数同已经生成的这四个Map比较,如果与其中一个吻合,就会直接读出缓存的属性偏移地址,拿到属性的值。
在这种情况下(即分析,归类这个call site可能出现的类型)V8最多会为4种类型生成对应的Map,当达到了生成Map的上限,不会再生成新的Map。
上面的访问过程如果用JavaScript代码形势来看优化编译器生成的内部代码,它的结构如下
//TurboFan emits something like this:
if (a.map == 0x43501231) { // Maps embedded in code.
return loadAtOffset(a, 0xc);
} else if (a.map == 0x99503211) {
return loadAtOffset(a, 0x10);
}else if (a.map == 0x99493211) {
return loadAtOffset(a, 0x6c);
}else if (a.map == 0x38503104) {
return loadAtOffset(a, 0x1b);
} else {
DEOPTIMIZE(); // Oh noes!
}
V8会去逐一检查传入的类型和优化编译器保存在缓存里面的已有四个类型比较,然后获取属性的偏移位置。当所有检查都失败,会退回到由解释器生成的字节码去执行。这时候就是通过传统的去属性表里面做hash查找的方式来访问属性,不再是通过直接读取缓存好的偏移地址。
所以,可以尽量让JavaScript对象保持一致,拥有相同的结构,即相同属性保持一致顺序(这种根据引擎来进行的优化一般是放在最后来做)。
比如上面四个对象变成这样:
{x:1,a:2,b:undefined,c:undefined,d:undefined}
{x:1,a:undefined,b:undefined,c:undefined,d:3}
{x:1,a:undefined,b:undefined,c:4,d:undefined}
{x:1,,a:undefined,b:5,c:undefined,d:undefined}
此外,如果如果多个函数指向了同一个优化代码,其中一个去优化,所有函数都会去优化。因为这意味着那时候优化后的机器码已经没有用了,所有函数也无法再访问到了。