- 原文地址:GET READY: A NEW V8 IS COMING, NODE.JS PERFORMANCE IS CHANGING.
- 原文作者:Node.js Foundation
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:Starrier
- 校對者:ClarenceC、moods445
做好準備:新的 V8 即將到來,Node.js 的效能正在改變。
本文由 David Mark Clements 和 Matteo Collina 共同撰寫,負責校對的是來自 V8 團隊的 Franziska Hinkelmann 和 Benedikt Meurer。起初,這個故事被髮表在 nearForm 的 blog 板塊。在 7 月 27 日文章釋出以來就做了一些修改,文章中對這些修改有所提及。
更新:Node.js 8.3.0 將會和 Turbofan 一起釋出在 V8 6.0 中 。用 NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/rc nvm i 8.3.0-rc.0
來驗證應用程式
自誕生之日起,node.js 就依賴於 V8 JavaScript 引擎來為我們熟悉和喜愛的語言提供程式碼執行環境。V8 JavaScipt 引擎是 Google 為 Chrome 瀏覽器編寫的 JavaScipt VM。起初,V8 的主要目標是使 JavaScript 更快,至少要比同類競爭產品要快。對於一種高度動態的弱型別語言來說,這可不是容易的事情。文章將介紹 V8 和 JS 引擎的效能演變。
允許 V8 引擎高速執行 JavaScript 的是其中一個核心部分:JIT(Just In Time) 編譯器。這是一個可以在執行時優化程式碼的動態編譯器。V8 第一次建立 JIT 編譯器的時候, 它被稱為 FullCodegen。之後 V8 團隊實現了 Crankshaft,其中包含了許多 FullCodegen 未實現的效能優化。
編輯:FullCodegen 是 V8 的第一個優化編譯器,感謝 Yang Guo 的報告
作為 JavaScript 自 90 年代以來的關注者和使用者,JavaScript(不管是什麼引擎)中快速或者緩慢的方法似乎往往是違法直覺的,JavaScript 程式碼緩慢的原因也常常難以理解。
最近幾年,Matteo Collina 和 我 致力於研究如何編寫高效能 Node.js 程式碼。當然,這意味著我們在用 V8 JavaScript 引擎執行程式碼的時候,知道哪些方法是高效的,哪些方法是低效的。
現在是時候挑戰所有關於效能的假設了,因為 V8 團隊已經編寫了一個新的 JIT 編譯器:Turbofan。
從更常見的 "V8 Killers"(導致優化程式碼片段的 bail-out--
在 Turbofan 環境下失效) 開始,Matteo 和我在 Crankshaft 效能方面所得到的模糊發現,將會通過一系列微基準測試結果和對 V8 進展版本的觀察來得到答案。
當然,在優化 V8 邏輯路徑前,我們首先應該關注 API 設計,演算法和資料結構。這些微基準測試旨在顯示 JavaScript 在 Node 中執行時是如何變化的。我們可以使用這些指標來影響我們的一般程式碼風格,以及改進在進行常用優化之後效能提升的方法。
我們將在 V8 5.1、5.8、5.9、6.0 和 6.1 中檢視微基準測試下它們的效能。
將上述每個版本都放在上下文中:V8 5.1 是 Node 6 使用的引擎,使用了 Crankshaft JIT 編譯器,V8 5.8 是 Node 8.0 至 8.2 的引擎,混合使用了 Crankshaft 和 Turbofan。
目前,5.9 和 6.0 引擎將在 Node 8.3(也可能是 Node 8.4)中,而 V8 6.1 是 V8 最新版本 (在編寫本報告時),它在 node-v8 倉庫 github.com/nodejs/node… 的實驗分支中與 Node 整合。換句話說,V8 6.1 版本將在後繼 Node 版本中使用。
讓我們看下微基準測試,另一方面,我們將討論這對未來意味著什麼。所有的微基準測試都由 benchmark.js](https://www.npmjs.com/package/benchmark) 執行,繪製的值是每秒運算元,因此在圖中越高越好。
TRY/CATCH 問題
最著名的去優化模式之一是使用 try/catch
塊。
在這個微基準測試中,我們比較了四種情況:
- 帶有
try/catch
的函式 (帶 try catch 的 sum) - 不含
try/catch
的函式 (不帶 try catch 的 sum) - 呼叫
try
塊中的函式 (sum 在 try 中) - 簡單的函式呼叫, 不涉及
try/catch
(sum 函式)
我們可以看到,在 Node 6 (V8 5.1) 圍繞 try/catch
引發效能問題是真實存在的,但是對 Node 8.0-8.2 (V8 5.8) 的效能影響要小得多。
值得注意的是,在 try
塊內部呼叫函式比從 try
塊之外呼叫函式慢得多 - 在 Node 6 (V8 5.1) 和 Node 8.0-8.2 (V8 5.8) 都是如此。
然而對於 Node 8.3+,在 try
塊內呼叫函式的效能影響可以忽略不計。
儘管如此,不要掉以輕心。在整理效能工作報告時,Matteo 和我發現了一個效能 bug,在特殊情況下 Turbofan 中可能會導致出現去優化/優化的無限迴圈 (被視為“killer” — 一種破壞效能的模式)。
從 Objects 中刪除屬性
多年來,delete
已經限制了很多希望編寫出高效能 JavaScript 的人(至少是我們試圖為熱路徑編寫最優程式碼的地方)。
delete
的問題歸結於 V8 在原生 JavaScript 物件的動態性質以及(可能也是動態的)原型鏈的處理方式上。這使得查詢在實現層面上的屬性查詢更加複雜。
V8 引擎快速生成屬性物件的技術是基於物件的“形狀”在 c++ 層建立類。形狀本質上是屬性所具有的鍵、值(包括原型鏈鍵值)。這些被稱為“隱藏類”。但是這是在執行時對物件進行優化,如果物件的型別不確定,V8 有另一種屬性檢索的模型:hash 表查詢。hash 表的查詢速度很慢。歷史上, 當我們從物件中 delete
一個鍵時,後續的屬性訪問將是一個 hash 查詢。 這是我們避免使用 delete
而將屬性設定為 undefined
以防止在檢查屬性是否已經存在時,導致結果與值相同的問題的產生的原因。 但對於預序列化已經足夠了,因為 JSON.stringify
輸出中不包含 undefined
(undefined
不是 JSON 規範中的有效值) 。
現在,讓我們看看更新 Turbofan 實現是否解決了 delete
問題。
在這個微基準測試中,我們比較如下三種情況:
- 在物件屬性設定為
undefined
後,序列化物件 - 在
delete
物件屬性後,序列化物件 - 在
delete
已被移出物件的最近新增的屬性後,序列化物件
在 V8 6.0 和 6.1 (尚未在任何 Node 發行版本中使用)中,Turbofan 會建立一個刪除最後一個新增到物件中的屬性的快捷方式,因此會比設定 undefined
更快。這是好訊息,因為它表明 V8 團隊正努力提高 delete
的效能。然而,如果從物件中刪除了一個不是最近新增的屬性, delete
操作仍然會對屬性訪問的效能帶來顯著影響。因此,我們仍然不推薦使用 delete
。
編輯: 在之前版本的帖子中,我們得出結論 elete
可以也應該在未來的 Node.js 中使用。但是 Jakob Kummerow 告訴我們,我們的基準測試只觸發了最後一次屬性訪問的情況。感謝 Jakob Kummerow!
顯式並且陣列化 ARGUMENTS
普通 JavaScript 函式 (相對於沒有 arguments
物件的箭頭函式 )可用隱式 arguments
物件的一個常見問題是它類似陣列,實際上不是陣列。
為了使用陣列方法或大多數陣列行為,arguments
物件的索引屬性已被複制到陣列中。在過去 JavaScripters 更傾向於將 less code和 faster code 相提並論。雖然這一經驗規則對瀏覽器端程式碼產生了有效負載大小的好處,但可能會對在伺服器端程式碼大小遠不如執行速度重要的情況造成困擾。因此將arguments
物件轉換為陣列的一種誘人的簡潔方案變得相當流行: Array.prototype.slice.call(arguments)
。呼叫陣列 slice
方法將 arguments
物件作為該方法的this
上下文傳遞, slice
方法從而將物件看做陣列一樣。也就是說,它將整個引數陣列物件作為一個陣列來分割。
然而當一個函式的隱式 arguments
物件從函式上下文中暴露出來(例如,當它從函式返回或者像 Array.prototype.slice.call(arguments)
時,會傳遞到另一個函式時)導致效能下降。 現在是時候驗證這個假設了。
下一個微基準測量了四個 V8 版本中兩個相互關聯的主題:arguments
洩露的成本和將引數複製到陣列中的成本 (隨後 函式作用域代替了 arguments
物件暴露出來).
這是我們案例的細節:
- 將
arguments
物件暴露給另一個函式 - 不進行陣列轉換 (洩露 arguments) - 使用
Array.prototype.slice
特性複製arguments
物件 (陣列的 prototype.slice arguments) - 使用 for 迴圈複製每個屬性 (for 迴圈複製引數)
- 使用 EcmaScript 2015 擴充套件運算子將輸入陣列分配給引用 (擴充套件運算子)
讓我們看一下線性圖形中的相同資料以強調效能特徵的變化:
要點如下:如果我們想要將函式輸入作為一個陣列處理,寫在高效能程式碼中 (在我的經驗中似乎相當普遍),在 Node 8.3 及更高版本應該使用 spread 運算子。在 Node 8.2 及更低版本應該使用 for 迴圈將鍵從 arguments
複製到另一個新的(預分配) 陣列中 (詳情請參閱基準程式碼)。
在 Node 8.3+ 之後的版本中,我們不會因為將 arguments
物件暴露給其他函式而受到懲罰, 因此我們不需要完整陣列並可以以使用類似陣列結構的情況下,可能會有更大的效能優勢。
部分應用 (CURRYING) 和繫結
部分應用(或 currying)指的是我們可以在巢狀閉包作用域中捕獲狀態的方式。
例如:
function add (a, b) {
return a + b
}
const add10 = function (n) {
return add(10, n)
}
console.log(add10(20))
複製程式碼
這裡 add
的引數 a
在 add10
函式中數值 10
部分應用。
從 EcmaScript 5 開始,bind
方法就提供了部分應用的簡潔形式:
function add (a, b) {
return a + b
}
const add10 = add.bind(null, 10)
console.log(add10(20))
複製程式碼
但是我們通常不用 bind
,因為它明顯比使用閉包要慢 。
這個基準測試了目標 V8 版本中 bind
和閉包之間的差異,並以之直接函式呼叫作為控制元件。
這是我們使用的四個案例:
- 函式呼叫另一個第一個引數部分應用的函式 (curry)
- 箭頭函式 (箭頭函式)
- 通過
bind
部分應用另一個函式的第一個引數建立的函式 (bind)。 - 直接呼叫一個沒有任何部分應用的函式 (直接呼叫)
基準測試結果的視覺化線性圖清楚地說明了這些方法在 V8 或者更高版本中是如何合併的。有趣的是,使用箭頭函式的部分應用比使用普通函式要快(至少在我們微基準情況下)。事實上它跟蹤了直接呼叫的效能特性。在 V8 5.1 (Node 6) 和 5.8(Node 8.0–8.2)中 bind
的速度顯然很慢,使用箭頭函式進行部分應用是最快的選擇。然而 bind
速度比 V8 5.9 (Node 8.3+) 提高了一個數量級,成為 6.1 (Node 後繼版本) 中最快的方法( 幾乎可以忽略不計) 。
使用箭頭函式是克服所有版本的最快方法。後續版本中使用箭頭函式的程式碼將偏向於使用 bind
,因為它比普通函式更快。但是,作為警告,我們可能需要研究更多具有不同大小的資料結構的部分應用型別來獲取更全面的情況。
函式字元數
函式的大小,包括簽名、空格、甚至註釋都會影響函式是否可以被 V8 內聯。是的:為你的函式新增註釋可能會導致效能下降 10%。Turbofan 會改變麼?讓我們找出答案。
在這個基準測試中,我們看三種情況:
- 呼叫一個小函式 (sum small function)
- 一個小函式的操作在內聯中執行,並加上註釋。(long all together)
- 呼叫已填充註釋的大函式 (sum long function)
Code: github.com/davidmarkcl…
在 V8 5.1 (Node 6) 中,sum small function 和 long all together 是一樣的。這完美闡釋了內聯是如何工作的。當我們呼叫小函式時,就好像 V8 將小函式的內容寫到了呼叫它的地方。因此當我們實際編寫函式的內容 (即使新增了額外的註釋填充)時, 我們已經手動內聯了這些操作,並且效能相同。在 V8 5.1 (Node 6) 中,我們可以再次發現,呼叫一個包含註釋的函式會使其超過一定大小,從而導致執行速度變慢。
在 Node 8.0–8.2 (V8 5.8) 中,除了呼叫小函式的成本顯著增加外,情況基本相同。這可能是由於 Crankshaft 和 Turbofan 元素混合在一起,一個函式在 Crankshaft 另一個可能 Turbofan 中導致內聯功能失調。(即必須在串聯行內函數的叢集間跳轉)。
在 5.9 及更高版本(Node 8.3+)中,由不相關字元(如空格或註釋)新增的任何大小都不會影響函式效能。這是因為 Turbofan 使用函式 AST (Abstract Syntax Tree 節點數來確定函式大小,而不是像在 Crankshaft 中那樣使用字元計數。它不檢查函式的位元組計數,而是考慮函式的實際指令,因此 V8 5.9 (Node 8.3+)之後 空格, 變數名字元數, 函式名和註釋不再是影響函式是否內聯的因素。
值得注意的是,我們再次看到函式的整體效能下降。
這裡的優點應該仍然是保持函式較小。目前我們必須避免函式內部過多的註釋(甚至是空格)。而且如果您想要絕對最快的速度,手動內聯(刪除呼叫)始終是最快的方法。當然還要與以下事實保持平衡:函式不應該在大小(實際可執行程式碼)確定後被內聯,因此將其他函式程式碼複製到您的程式碼中可能會導致效能問題。換句話說,手動內聯是一種潛在方法:大多數情況下,最好讓編譯器來內聯。
32BIT 整數 VS 64BIT 整數
眾所周知,JavaScript 只有一種資料型別:Number
。
但是 V8 是用 C++ 實現的,因此必須在 JavaScript 數值的底層基礎型別上進行選擇。
對於整數 (也就是說,當我們在 JS 中指定一個沒有小數的數字時), V8 假設所有的數字都是 32 位--直到它們不是的時候。 這似乎是一個合理的選擇,因為多數情況下,數字都在 2147483648–2147483647 範圍之間。 如果 JavaScript (整) 數超過 2147483647,JIT 編譯器必須動態地將該數字基礎型別更改為 double (雙精度浮點數) — 這也可能對其他優化產生潛在的影響。
以下三個基準測試案例:
- 只處理 32 位範圍內的數字的函式 (sum small)
- 處理 32 位和 double 組合的函式 (from small to big)
- 只處理 double 型別數字的函式 (all big)
Code: github.com/davidmarkcl…
我們可以從圖中看出,無論是在 Node 6 (V8 5.1) 還是 Node 8 (V8 5.8) 甚至是 Node 的後繼版本,這些觀察都是正確的。使用大於 2147483647 數字(整數)的操作將導致函式執行速度在一半到三分之二之間。因此,如果您有很長的數字 ID—將他們放在字串中。
同樣值得注意的是,在 32 位範圍內的數字操作在 Node 6 (V8 5.1) 和 Node 8.1 以及 8.2 (V8 5.8) 有速度增長,但是在 Node 8.3+ (V8 5.9+)中速度明顯降低。然而在 Node 8.3+ (V8 5.9+)中,double 運算變得更快,這很可能是(32位)數字處理速度緩慢,而不是函式或與 for
迴圈 (在基準程式碼中使用)速度有關
編輯: 感謝 Jakob Kummerow 和 Yang Guo 已經 V8 團隊對結果的準確性和精確性的更新。
迭代物件
獲得物件的所有值並對它們進行處理是常見的操作,而且有很多方法可以實現。讓我們找出在 V8 (和 Node) 中最快的那個版本。
這個基準測試的四個案例針對所有 V8 版本:
- 在
for
-in
迴圈中使用hasOwnProperty
方法來檢查是否已經獲得物件值。 (for in) - 使用
Object.keys
並使用陣列的reduce
方法迭代鍵,訪問 iterator 函式中提供給的物件值 (函式式 Object.keys) - 使用
Object.keys
並使用陣列的reduce
方法迭代鍵,訪問 iterator 函式中的物件值,提供給reduce
的迭代函式中物件值,以減少 iterator 是箭頭函式的位置 (函式式箭頭函式 Object.keys) - 迴圈訪問使用
for
迴圈從Object.keys
返回的陣列的每個物件值 (**for 迴圈 Object.keys **)
我們還為V8 5.8、5.9、 6.0 和 6.1 增加了三個額外的基準測試案例
- 使用
Object.values
和陣列reduce
方法遍歷值, (函式式 Object.values) - 使用
Object.values
和陣列reduce
方法遍歷值,其中提供給reduce
的 iterator 函式是箭頭函式 (函式式箭頭函式 Object.values) - 使用
for
迴圈遍歷從Object.values
中返回的陣列 (for 迴圈 Object.values)
在 V8 5.1 (Node 6)中,我們不會支援這些情況,因為它不支援原生 EcmaScript 2017 Object.values
方法。
Code: github.com/davidmarkcl…
在 Node 6 (V8 5.1) 和 Node 8.0–8.2 (V8 5.8) 中,遍歷物件的鍵然後訪問值使用 for
-in
是迄今為止最快的方法。4 千萬 op/s 比下一個接近 Object.keys
的方法(大約 8 百萬 op/s)快了近5倍。
在 V8 6.0 (Node 8.3) 中 for
-in
發生了改變,它降低至之前版本速度的四分之三,但仍然比任何方法速度都快。
在 V8 6.1 (Node 後繼版本)中,Object.keys
比使用for
-in
的速度有所提升 -但在 V8 5.1 和 5.8 (Node 6, Node 8.0-8.2) 中,仍然不及 for
-in
的速度。
Turbofan 背後的執行原理似乎是對直觀的編碼行為進行優化。也就是說,對開發者最符合人體工程學的情況進行優化。
使用 Object.values
直接獲取值比使用 Object.keys
並訪問物件值要慢。最重要的是,程式迴圈比函數語言程式設計要快。因此在迭代物件時可能要做更多的工作。
此外,對那些為了提升效能而使用 for
-in
卻因為沒有其他選擇而失去大部分速度的人來說,這是一個痛苦的時刻。
建立物件
我們始終在建立物件,所以這是一個很好的測量領域。
我們要看三個案例:
- 建立物件時使用物件字面量 (literal)
- 建立物件時使用 ECMAScript 2015 類 (class)
- 建立物件時使用建構函式 (constructor)
Code: github.com/davidmarkcl…
在 Node 6 (V8 5.1) 中所有方法都一樣。
在 Node 8.0–8.2 (V8 5.8)中,從 EcmaScript 2015 類建立例項的速度不及用物件字面量或者建構函式速度的一半。所以,你知道後要注意這一點。
在 V8 5.9 中,效能再次均衡。
然後在 V8 6.0 (可能是 Node 8.3,或者是 8.4) 和 6.1 (目前尚未釋出在任何 Node 版本) 中物件建立速度 簡直瘋狂!!超過了 500 百萬 op/s!令人難以置信。
我們可以看到由建構函式建立物件稍慢一些。因此,為了對未來友好的高效能程式碼,我們最好的選擇是始終使用物件字面量。這很適合我們,因為我們建議從函式(而不是使用類或建構函式)返回物件字面量作為一般的最佳編碼實踐。
編輯:Jakob Kummerow 在 http://disq.us/p/1kvomfk 中指出,Turbofan 可以在這個特定的微基準中優化物件分配。考慮這一點,我們會盡快重新進行更新。
單態函式與多型函式
當我們總是將相同型別的 argument 輸入到函式中(例如,我們總是傳遞一個字串)時,我們就以單態形式使用該函式。一些函式被編寫成多型 -- 這意味著相同的引數可以作為不同的隱藏類處理 -- 所以它可能可以處理一個字串、一個陣列或一個具有特定隱藏類的物件,並相應地處理它。在某些情況下,這可以提供良好的介面,但會對效能產生負面影響。
讓我們看看單態和多型在基準測試的表現。
在這裡,我們研究五個案例:
- 函式同時傳遞物件字面量和字串 (多型字面量)
- 函式同時傳遞建構函式例項和字串 (多型建構函式)
- 函式只傳遞字串 (單態字串)
- 函式只傳遞字面量 (單態字面量)
- 函式只傳遞建構函式例項 (帶建構函式的單例物件)
圖中的視覺化資料表明,在所有的 V8 測試版本中單態函式效能優於多型函式。
這進一步說明了在 V8 6.1(Node 後繼版本)中,單態函式和多型函式之間的效能差距會更大。不過值得注意的是,這個基於使用了一種 nightly-build 方式構建 V8 版本的 node-v8 分支的版本 -- 可能最終不會成為 V8 6.1 中的一個具體特性
如果我們正在編寫的程式碼需要是最優的,並且函式將被多次呼叫,此時我們應該避免使用多型。另一方面,如果只呼叫一兩次,比如例項化/設定函式,那麼多型 API 是可以接受的。
編輯:V8 團隊已經通知我們,使用其內部可執行檔案 _d8_
無法可靠地重現此特定基準測試的結果。然而,這個基準在 Node 上是可重現的。因此,應該考慮到結果和隨後的分析,可能會在之後的 Node 更新中發生變化(基於 Node 和 V8 的整合中)。不過還需要進一步分析。感謝 Jakob Kummerow 指出了這一點。
DEBUGGER
關鍵詞
最後,讓我們討論一下 debugger
關鍵詞。
確保從程式碼中刪除了 debugger
語句。散亂的 debugger
語句會破壞效能。
我們看下以下兩種案例:
- 包含
debugger
關鍵詞的函式 (帶有 debugger) - 不包含
debugger
關鍵詞的函式 (不含 debugger)
Code: github.com/davidmarkcl…
是的,debugger
關鍵詞的存在對於測試所有 V8 版本的效能來說都很糟糕。
在沒有 debugger 行的那些 V8 版本中,效能顯著提升。我們將在總結中討論這一點。
真實世界的基準: LOGGER 比較
除了微基準測試,我們還可以通過使用 Node.js 最流行的日誌(Matteo 和我建立的 Pino 時編寫的)來檢視 V8 版本的整體效果。
下面的條形圖表明在Node.js 6.11 (Crankshaft) 中最受歡迎的 logger 記錄1萬行(更低些會更好) 日誌所用時間:
以下是使用 V8 6.1 (Turbofan) 的相同基準:
雖然所有的 logger 基準測試速度都有所提高 (大約是 2 倍),但 Winston logger 從新的 Turbofan JIT 編譯器中獲得了最大的好處。這似乎證明了我們在微基準測試中看到的各種方法之間的速度趨於一致:Crankshaft 中較慢的方法在 Turbofan 中明顯更快,而在 Crankshaft 的快速方法在 Turbofan 中往往會稍慢。Winston 是最慢的,可能是使用了在 Crankshaft 中較慢而在 Turbofan 中更快的方法,然而 Pino 使用最快的 Crankshaft 方法進行優化。雖然在 Pino 中觀察到速度有所增加,但是效果不是很明顯。
總結
一些基準測試表明,隨著 V8 6.0 和 V8 6.1中全部啟用 Turbofan,在 V8 5.1, V8 5.8 和 5.9 中的緩慢情況有所加速 ,但快速情況也有所下降,這往往與緩慢情況的增速相匹配。
很大程度上是由於在 Turbofan (V8 6.0 及以上) 中進行函式呼叫的成本。Turbofan 的核心思想是優化常見情況並消除“V8 Killers”。這為 (Chrome) 瀏覽器和伺服器 (Node)帶來了淨效益。 對於大多數情況來說,權衡出現在(至少是最初)速度下降。基準日志比較表明,Turbofan 的總體淨效應即使在程式碼基數明顯不同的情況下(例如:Winston 和 Pino) 也可以全面提高。
如果您關注 JavaScript 效能已經有一段時間了,也可以根據底層引擎改善編碼方式,那麼是時候放棄一些技術了。如果您專注於最佳實踐,編寫一般的 JavaScript,那麼很好,感謝 V8 團隊的不懈努力,高效效能時代即將到來。
本文的作者是 David Mark Clements 和 Matteo Collina, 由來自 V8 團隊的 Franziska Hinkelmann 和 Benedikt Meurer 校對。
本文的所有原始碼和文章副本都可以在 github.com/davidmarkcl… 上找到。
文章的原始資料可以在docs.google.com/spreadsheet…。
大多數的微基準測試是在 Macbook Pro 2016 上進行的,16 GB 2133 MHz LPDDR3 的 3.3 GHz Intel Core i7,其他的 (數字、屬性已經刪除) 則執行在 MacBook Pro 2014,16 GB 1600 MHz DDR3的 3 GHz Intel Core i7 。Node.js 不同版本之間的測試都是在同一臺機器上進行的。我們已經非常小心地確保不受其他程式的干擾。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。