以前看了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