V8是google開發的JavaScript引擎, 它是開源的 ,並且是用C++編寫的。它是用於客戶端(Google Chrome)和服務器端(node.js)JavaScript應用程序。java
V8最初旨在提升Web瀏覽器中JavaScript執行的性能。爲了提高速度,V8將JavaScript代碼轉換爲更高效的機器語言,而不是使用解釋器。它經過實現 JIT(即時編譯器)將JavaScript代碼編譯成機器代碼,就像許多現代JavaScript引擎(如SpiderMonkey或Rhino(Mozilla))所作的那樣。與V8的主要區別在於它不會產生字節碼或任何中間代碼。node
本文的目的是展現和理解 V8如何工做,以便爲客戶端或服務器端應用程序生成優化的代碼。若是您已經在問本身「我應該關心JavaScript性能嗎?」那麼我將回答Daniel Clifford(技術主管和V8團隊經理)的一句話:「這不只僅是讓您當前的應用程序運行得更快,而是關於實現你過去從未作過的事情「。編程
JavaScript是一種基於原型的語言:no classes,而且使用克隆過程建立對象(原型鏈)。JavaScript也是動態類型的:類型和類型信息不是顯式的,屬性能夠動態添加到對象中或從中刪除。有效訪問類型和屬性是V8的首要挑戰。而不是使用相似字典的數據結構來存儲對象屬性和進行動態查找來解析屬性位置(就像大多數JavaScript引擎同樣),V8在運行時建立隱藏類,以便具備內部表示類型系統和改善屬性訪問時間。數組
讓咱們有一個Point函數和兩個Point對象的建立:瀏覽器
https://p1.ssl.qhimg.com/t016...
緩存
若是佈局相同(這裏是這種狀況),則p和q屬於由V8建立的相同隱藏類。這突出了使用隱藏類的另外一個優勢:它容許V8對屬性相同的對象進行分組。這裏p和q有必定的代碼優化。服務器
如今,讓咱們假設咱們想在咱們的q對象以後添加一個z屬性,就在它聲明以後(對於動態類型語言來講這是徹底沒問題的)。數據結構
V8將如何處理這種狀況?事實上,每當構造函數聲明一個屬性並跟蹤隱藏類的變化時,V8 就會建立一個新的隱藏類。爲何?由於若是建立了兩個對象(p和q)而且在建立後將成員添加到第二個對象(q),則V8須要保留最後建立的隱藏類(對於第一個對象p)並建立一個新對象(對於第二個對象q)與新成員。app
https://p4.ssl.qhimg.com/t01c...
編程語言
每次建立一個新的隱藏類時,前一個隱藏類都會更新一個類轉換,指示必須使用哪一個隱藏類。
所以:
由於V8爲每一個屬性建立一個新的隱藏類,因此應該將隱藏的類建立保持在最低限度。爲此,請儘可能避免在建立對象後添加屬性,並始終以相同的順序初始化對象成員(以免建立不一樣的隱藏類樹)。
[Update ]另外一個技巧:單態操做是僅對具備相同隱藏類的對象起做用的操做。當咱們調用一個函數時,V8會建立一個隱藏類。若是咱們用不一樣的參數類型再次調用它,V8須要建立另外一個隱藏類:首選單態代碼到多態代碼
爲了有效地表示數字和JavaScript對象,V8表示具備 32位值。它使用一個位來知道它是一個對象(flag = 1)仍是一個整數(flag = 0),這裏稱爲SMall Integer或 SMI ,由於它的31位。而後,若是數值大於31位,則V8將對該數字進行選擇,將其變爲雙精度並建立一個新對象以將數字放入其中。
代碼優化:儘量使用31位帶符號數字,以免對JavaScript對象進行消耗性能的封裝操做。
V8使用兩種不一樣的方法來處理數組:
代碼優化:確保V8使用「快速元素」來處理數組,換句話說,避免使用稀疏數組。另外,儘可能避免預先分配大型數組。最後,不要刪除數組中的元素:它使鍵集稀疏。
a = new Array(); for (var b = 0; b < 10; b++) { a[0] |= b; // Oh no! } //vs. a = new Array(); a[0] = 0; for (var b = 0; b < 10; b++) { a[0] |= b; // Much better! 2x faster. }
此外,雙精度陣列更快 - 數組的隱藏類跟蹤元素類型,而且僅包含雙精度的數組是未裝箱的(這會致使隱藏的類更改)。可是,因爲裝箱和拆箱,粗心操做陣列會致使額外的工做 - 例如
var a = new Array(); a[0] = 77; // Allocates a[1] = 88; a[2] = 0.5; // Allocates, converts a[3] = true; // Allocates, converts
效率低於:
var a = [77, 88, 0.5, true];
一個「完整」編譯器,能夠爲任何JavaScript生成良好的代碼。此編譯器的目標是快速生成代碼。爲了實現其目標,它不進行任何類型分析,也不瞭解類型。相反,它使用內聯緩存或「IC」策略來在程序運行時優化有關類型的知識。IC效率很是高,速度可提升20倍。
優化編譯器,可爲大多數JavaScript語言生成出色的代碼。它稍後會從新編譯熱門功能。優化編譯器從內聯緩存中獲取類型,並決定如何更好地優化代碼。可是,某些語言功能尚不支持,例如try / catch塊。(try / catch塊的解決方法是在函數中編寫「非穩定」代碼並在try塊中調用函數)
代碼優化:V8還支持去優化:優化編譯器從內聯緩存中對不一樣類型作出假設,若是這些假設無效則會進行去優化。例如,若是生成的隱藏類不是預期的類,則V8會拋棄優化的代碼並返回到完整編譯器以從內聯緩存中再次獲取類型。此過程很慢,應該經過在優化後嘗試不更改功能來避免。
資源
譯者注:
關於本文中提到的一些知識點,作一些簡單的只是擴展,但願對大家理解本文有一些幫助;
一、 "JavaScript has no classes"
雖然JavaScript是面向對象的語言,但它不是基於類的語言 - 它是基於原型的語言。
在js和java或其餘「基於類」的編程語言中類的工做方式之間存在一些深入的差別。
相關討論
二、快速元素和字典元素
快速或字典元素:元素的第二個主要區別是它們是快速仍是字典模式。快速元素是簡單的VM內部數組,其中屬性索引映射到元素存儲中的索引。可是,這種簡單的表示對於很是大的稀疏/多孔數組而言是至關浪費的,其中只佔用不多的條目。在這種狀況下,咱們使用基於字典的表示來節省內存,但代價是訪問速度稍慢:
const sparseArray = []; sparseArray[9999] = 'foo'; // Creates an array with dictionary elements. sparseArray.length // 10000 sparseArray[0] // undefined
在這個例子中,分配一個包含10k條目的完整數組會至關浪費。相反,V8會建立一個字典來存儲鍵值描述符三元組。在這種狀況下,密鑰是'9999',而且使用值'foo'和默認描述符。鑑於咱們沒有辦法在HiddenClass上存儲描述符詳細信息,只要您使用自定義描述符定義索引屬性,V8就會轉向減慢元素:
const array = []; Object.defineProperty(array, 0, {value: 'fixed' configurable: false}); console.log(array[0]); // Prints 'fixed'. array[0] = 'other value'; // Cannot override index 0. console.log(array[0]); // Still prints 'fixed'.