標籤:大小 數組 需要 undefined 核心 github 不容易 資料 函數返回
Node.js 就依賴於 V8 引擎, V8 引擎是由 Google 為 Chrome 瀏覽器編寫的 JavaScript 虛擬機器。從一開始,V8 的主要目標就是使 JavaScript 運行更加快速,或至少比競爭者快。對於高度動態這並不容易。這部分是關於 V8 和 JS 引擎效能的演變。
V8 引擎的核心部分是能夠高速執行 JavaScript 的 JIT(Just In Time)編譯器。這是一個動態編譯器,可以在運行時最佳化代碼。當 V8 最初構建時,JIT 編譯器被稱為 FullCodegen。然後,V8 團隊實現了 Crankshaft,其中包括許多 FullCodegen 未實現的效能最佳化。
從 90 年代開始,JavaScript 哪些操作快,哪些操作慢(無論引擎是什麼)通常都是反直覺的,所以JavaScript 代碼慢的原因常常難以理解。
近年來,Matteo Collina 和我專註於如何編寫效能優異的 Node.js 代碼。當然,這意味著當 V8 引擎執行代碼時,要知道哪些方法是快速的,哪些方法很慢。
現在是我們挑戰所有關於效能假設的時候了,因為 V8 團隊已經編寫了一個新的 JIT 編譯器:Turbofan。
從常見的“V8殺手”開始,到更複雜的 Matteo 和我對 Crankshaft 效能的發現,我們將會根據一系列測試結果來對 V8 進行評估。
當然,在最佳化 V8 代碼之前,我們應該首先關注 API 設計,演算法和資料結構(過早的最佳化是萬惡之源)。下面會對 Node 的幾個版本進行效能測試。我們可以得到一些結論來影響我們的代碼風格,以及在應用通常的最佳化後提高效能的方式。
Try / Catch 問題
一個非常出名的去最佳化模式是使用 try/catch blocks.
測試代碼連結(https://github.com/davidmarkclements/v8-perf/blob/master/bench/try-catch.js) 分別測試如下 4 種情況
函數中有 try/catch
函數中不帶 try/catch
在 try 中調用函數
調用函數(沒有 try/catch)
測試資料
?
我們可以看到 node 6 ( V8 5.1 ) 中 try/catch 的效能問題確實存在,但在 node 8 到 node 8.2 得到明顯的改善。
另外值得注意的是, 在 try block 中調用函數明顯比在 try block 外面要慢。
到 node 8.3 以上的版本 try/catch 對調用函數的影響基本可以忽略不計。
刪除對象的屬性
由於 javascript 對象的動態特性和原型鏈,導致屬性尋找更加複雜。
這裡使用 3 種方式刪除對象屬性(測試代碼https://github.com/davidmarkclements/v8-perf/blob/master/bench/property-removal.js)。
通過設定直接給對象屬性設為 undefined
通過 delete 刪除屬性
刪除最近添加的屬性
?
在 Turbofan (V8 6.0 and 6.1)可以發現刪除最近添加屬性的效率更高,通過給對象屬性直接設為 undefined 的效率也是非常高的。而 delete 操作在大部分情況表現不佳。
Arguments
普通的 JavaScript 函數可用隱式參數對象,它很像數組。
為了使用數組方法或大多數數組行為,參數對象的索引屬性已被複製到數組中。在過去,JavaScript 中人們傾向於將較少的代碼等同於與較快的代碼。雖然這個經驗法則為瀏覽器端代碼帶來了承載的好處,但同樣的規則導致了伺服器端代碼大小遠不如其執行速度重要的痛苦。所以將參數對象轉換為數組的一種簡潔方法就變得非常受歡迎:
Array.prototype.slice.call(arguments)。這調用 Array slice 方法傳遞參數對象作為該方法的上下文,slice 方法會得到一個像數組一樣的對象,並相應地執行。也就是說,它將整個參數array-like 對象作為一個數組。
但是,當函數的隱式參數對象從函數上下文中暴露出來(例如,當從函數返回或傳遞到另一個函數中時,如 Array.prototype.slice.call(arguments)所示),這通常會導致效能下降。
測試代碼(https://github.com/davidmarkclements/v8-perf/blob/master/bench/arguments.js)
暴露 arguments 對象給其他的函數
使用 Array.prototype.slice 複製 arguments 對象
使用迴圈複製對象的每一個屬性
使用 es6 的展開操作符
?
我們再把這些資料轉化成折線圖:
?
如果我們想要將函數輸入的參數寫成一個數組,在 Node 8.3 以上版本我們建議使用 spread 操作符。在 Node 8.2 及以下版本我們最好通過迴圈複製對象屬性到一個新的數組以提升效能。
對於 node 8.3 及以上版本,將參數對象直接暴露給其他的函數的情況效能表現很不錯,因此在不需要把參數寫成數組,這種方式可以大大提升代碼執行效率。
新 V8 即將推出和 Node.js