以前看了wizard大神关于js极致优化的演讲,优化主要包括3个方向html
下来本身也搜了一些关于monomorphism的文章,这里作一个总结。java
直译过来是单一形态,什么意思呢? 大概就是说咱虽然是动态类型的语言吧,可是最好也别太飘了,对象的类型能稳定就稳定,别总是作删属性,增属性这种操做。引用v8大神Vyacheslav Egorov的话来讲就是:缓存
nicest dynamic behavior is static-likebash
怎么翻译我也不知道,总之就是代码写的越像静态的越好(感受在变相给ts点赞)。闭包
要弄清楚缘由,咱得先弄明白咱得对象都是怎么存的。app
在一些静态类型语言里面,好比java,由于类的每一个属性的类型已经定了,因此每一个类型要占的内存大小也能够肯定,因此在compile的时候,每一个属性的offset等信息都是能够肯定的。这样一来,访问一个属性就会很是快。函数
而在js这种动态类型的语言里面,compile的时候显然无法知道每一个属性在内存里的offset究竟是多少。 那js里面是怎么找到属性的内存地址的呢?性能
js的vm,好比v8,会给每个咱们建立的对象,都生成一个对应的hidden class。 这个hidden class上会存属性的一些metadata以及在内存里的offset。测试
那么问题来了?这些值直接存object上不就行了么?干吗还再搞一个hidden class的概念出来?优化
缘由是为了节省内存,hidden class是能够共享的。 好比下面这例子:
let a = {x: 2, y: 3}
let b = {x: 3, y: 4}
复制代码
对象a和b的property以及类型彻底一致,咱们彻底可使用一个hidden class来描述这两个对象。 如此一来,管你成千上万个对象,只要你的属性同样,一个hidden class就搞定了。
若是对象b动态的增长了一个属z,会是什么状况?
显然,咱们无法直接取修改以前的hidden class, 由于对象a并无也增长这么一个属性. 取而代之的操做是会生成一个新的hidden class,描述新的属性c的metadata和offset。 而且新生成的hidden class会指向以前的hidden class,这个结构的学名叫Transition Chain。当咱们访问一个对象属性的时候,会先从最新的hidden class找起。
若是b这个对象继续增长新的属性,transition chain上的hidden class也会愈来愈多。那么就会有一个问题,加如我新加了100个属性,那为了找到最开始的属性值,我得遍历100个hidden class才能拿到最终的offset。 这个性能显然很差。
为了解决这个问题,v8采用了inline cache。
inline cache背后的概念很简单:当我对对象和属性类型的预测是对的,那中间的计算就省略了,直接从缓存里取offset值就完事了。
ok,inline cache很牛逼,但它都在哪些地方用呢?
js里的每一个函数都会被封装在一个闭包里面,闭包里面除了函数以外, 就是一堆对hidden class的inline cache。
好比这个例子:
function getX(o) {
return o.x;
}
getX({x: 1})
getX({x: 2})
复制代码
函数getX在第一遍被调用后,会用{x:1}的hidden class做为cache entry作好缓存。第二遍调用的时候,由于hidden class同样,因此就直接从缓存里拿offset了。
须要注意的一点是,inline cache的cache entry并非只有一个,能够有多个,cache entry的数量直接决定了读取的性能。 根据cache entry数量不一样,inline cache分为了3种状态:
那三种状态究竟速度差多少呢? 能够看下这个测试:
megamorphic状态下的对象读操做大概要比monomorphic慢86%左右,性能差异能够说是很是显著了。
A sneak peek into super optimized code in JS frameworks by Maxim Koretskyi | JSConf EU 2019
What's up with monomorphism? Explaining JavaScript VMs in JavaScript - Inline Caches