什么是内联函数
在 V8 执行函数的过程中,会观察函数中一些调用点 (CallSite) 上的关键的中间数据,然后将这些数据缓存起来,当下次再次执行该函数的时候,V8 就可以直接利用这些中间数据,节省了再次获取这些数据的过程,因此 V8 利用 IC,可以有效提升一些重复代码的执行效率。
IC 会为每个函数维护一个反馈向量 (FeedBack Vector),反馈向量记录了函数在执行过程中的一些关键的中间数据。关于函数和反馈向量的关系你可以参看下图:
反馈向量其实就是一个表结构,它由很多项组成的,每一项称为一个插槽 (Slot),V8 会依次将执行 loadX 函数的中间数据写入到反馈向量的插槽中。
function loadX(o) {
o.y = 4
return o.x
}
当 V8 执行这段函数的时候,它会判断 o.y = 4 和 return o.x 这两段是调用点 (CallSite),因为它们使用了对象和属性,那么 V8 会在 loadX 函数的反馈向量中为每个调用点分配一个插槽。
每个插槽中包括了插槽的索引 (slot index)、插槽的类型 (type)、插槽的状态 (state)、隐藏类 (map) 的地址、还有属性的偏移量,比如上面这个函数中的两个调用点都使用了对象 o,那么反馈向量两个插槽中的 map 属性也都是指向同一个隐藏类的,因此这两个插槽的 map 地址是一样的。
比如,我们关注一下 LdaNamedProperty 这个字节码,我们看到它有三个参数。a0 就是 loadX 的第一个参数;第二个参数[0]表示取出对象 a0 的第一个属性值,这两个参数很好理解。第三个参数就和反馈向量有关了,它表示将 LdaNamedProperty 操作的中间数据写入到反馈向量中,方括号中间的 0 表示写入反馈向量的第一个插槽中。
一个插槽,还可以包含多个隐藏类的信息,即多态。
function loadX(o) {
return o.x
}
var o = { x: 1,y:3}
var o1 = { x: 3, y:6,z:4}
for (var i = 0; i < 90000; i++) {
loadX(o)
loadX(o1)
}