通過衡量執行某個函數所花費的時間,以“證明”某些實作比另一些實作更高效始終是一個很好的主意。這也是確定性能在進行一些修改後不受影響并找出瓶頸的好方法。
良好的性能有助于獲得良好的使用者體驗。良好的使用者體驗能夠留住使用者。像此研究(http://www.mcrinc.com/Documents/Newsletters/201110_why_web_performance_matters.pdf)所示,由于性能差的使用者體驗,88% 的線上消費者回頭的可能性較小問題。
是以重要的是能夠識别代碼中的瓶頸并進行評估改進。特别是在為浏覽器開發 JavaScript 時,重要的是應該意識到,你編寫的每一行 JavaScript 都可能會阻塞 DOM,因為它是單線程語言。
在本文中,我将解釋如何測量函數的性能,以及如何從函數中獲得結果。
如果你發現某些計算過于繁瑣而無法在主線程上進行計算,則你甚至可以考慮将其放入服務或 Web Worker 中。我在這篇文章(https://felixgerschau.com/how-to-communicate-with-service-workers)中解釋了如何傳遞和接收來自 Service Workers 的資料。
Perfomance.now
高性能API通過其函數
performance.now()
提供對 DOMHighResTimeStamp 的通路,該函數傳回自頁面加載時間(以毫秒為機關),精度最高為 5µs(以分數為機關)。
是以在實踐中,你需要擷取兩個時間戳,将它們儲存在變量中,然後用第一個時間戳減去删除第二個時間戳:
1const t0 = performance.now();
2for (let i = 0; i < array.length; i++)
3{
4 // some code
5}
6const t1 = performance.now();
7console.log(t1 - t0, 'milliseconds');
複制
輸出(Chrome):
10.6350000001020817 "milliseconds"
複制
輸出(Firefox):
11 milliseconds
複制
在這裡,我們可以看到 Firefox 中的結果與 Chrome 完全不同。這是因為從版本 60 開始,Firefox 将 performance API 的精度降低到了 2ms。你可以在本文的末尾找到有關此内容的更多資訊。
performance API 提供的功能比僅傳回時間戳要多得多。它可以測量導航時間、使用者時間或資源時間。檢視本文(https://blog.logrocket.com/how-to-practically-use-performance-api-to-measure-performance/),其中有更詳細的說明。
但是對于我們的用例,隻想測量單個函數的性能,是以時間戳就足夠了。
與 Date.now 有什麼不同嗎?
現在你可能會想,嘿,我也可以用
Date.now
。
是的,你可以,但是有缺點。
Date.now
以毫秒為機關傳回自 Unix 元年(1970-01-01T00:00:00Z)以來經過的時間,并取決于系統時鐘。這不僅意味着它不夠精确,而且還并非總是遞增。WebKit 工程師(Tony Gentilcore)的解釋如下:
也許很少被考慮到的是,基于系統時間的日期也不适合實際監視。大多數系統運作一個守護程式,該守護程式負責定期同步時間。通常每 15 至 20 分鐘會把時鐘調整幾毫秒。以這種速度,以 10 秒間隔來說,大約 1% 将會是不準确的。
Console.time
該 API 确實好用。隻需将
console.time
放置在要測量的代碼之前,将
console.timeEnd
放在要測量的代碼之後,即可用相同的
string
參數調用該函數。一個頁面上最多可以同時使用 10,000 個計時器。
精度與 performance API 相同,但這又取決于浏覽器。
1console.time('test');
2for (let i = 0; i < array.length; i++) {
3 // some code
4}
5console.timeEnd('test');
複制
這将會自動生成人類可讀的輸出,如下所示:
輸出(Chrome):
1test: 0.766845703125ms
複制
輸出(Firefox):
1test: 2ms - timer ended
複制
此處的輸出仍然與 Performance API 非常相似。
console.time
的優點是容易使用,因為它不需要手動計算兩個時間戳之間的差。
時間精度降低
如果你在不同的浏覽器中使用上述API來評估函數,你可能會注意到結果會有所不同。
這是由于浏覽器試圖保護使用者免受 timing 攻擊 和指紋識别,如果時間戳過于準确,黑客可以使用它來識别使用者。
像 Firefox 這樣的浏覽器試圖通過把精度降低到 2ms(60版)來防止這種情況。
注意事項
現在你已經擁有了測量 JavaScript 函數運作速度所需的工具。但是還要避免一些陷阱:
分而治之
在篩選某些結果時發現速度很慢,但你不知道瓶頸在哪裡。
你可以用上面提到的這些函數來度量代碼,而不必去猜測到底史哪一部分代碼慢。
首先要跟蹤它,把
console.time
語句放在執行緩慢的代碼塊前後。然後評估他們不同部分的表現。如果一個比另一個慢,那就繼續往下走,直到發現瓶頸為止。
這些語句之間的代碼越少,則跟蹤到不感興趣的内容的可能性就越小。
注意輸入值
在實際應用中,給定函數的輸入值可能會發生很大變化。如果僅針對任意随機值測量函數,那麼速度并不能為我們提供任何有實用價值的資料。
要確定運作代碼時使用的輸入值是相同的。
多次運作函數
假設有一個函數可以周遊數組,并對每個值進行一些計算,然後傳回包含結果的數組。你想知道
forEach
或簡單的
for
循環哪個更有效。
這些是函數:
1function testForEach(x) {
2 console.time('test-forEach');
3 const res = [];
4 x.forEach((value, index) => {
5 res.push(value / 1.2 * 0.1);
6 });
7
8 console.timeEnd('test-forEach')
9 return res;
10}
11
12function testFor(x) {
13 console.time('test-for');
14 const res = [];
15 for (let i = 0; i < x.length; i ++) {
16 res.push(x[i] / 1.2 * 0.1);
17 }
18
19 console.timeEnd('test-for')
20 return res;
21}
複制
然後像這樣測試它們:
1const x = new Array(100000).fill(Math.random());
2testForEach(x);
3testFor(x);
複制
如果在 Firefox 中運作上述函數,你将獲得類似下面的輸出:
1test-forEach: 27ms - timer ended
2test-for: 3ms - timer ended
複制
看起來 forEach 比較慢,對吧?
讓我們看看用相同的輸入對相同的函數兩次運作:
1testForEach(x);
2testForEach(x);
3testFor(x);
4testFor(x);
5
6test-forEach: 13ms - timer ended
7test-forEach: 2ms - timer ended
8test-for: 1ms - timer ended
9test-for: 3ms - timer ended
複制
如果我們第二次調用
forEach
測試,則其性能與
for
循環一樣。考慮到初始值較慢,可能仍然不值得使用
forEach
。
…還有在多個浏覽器中
如果我們在 Chrome 中運作上述代碼,結果會突然看起來不同:
1test-forEach: 6.156005859375ms
2test-forEach: 8.01416015625ms
3test-for: 4.371337890625ms
4test-for: 4.31298828125ms
複制
這是因為 Chrome 和 Firefox 的 JavaScript 引擎是不同的,并且性能優化的類型也不同。能夠意識到這些差異是一件好事。
在這種情況下,Firefox 的優化在
forEach
方面做得比 Chrome 更好。
for
在兩個引擎上的性能都更好,是以最好堅持
for
循環。
這是一個很好的例子,說明了為什麼應該在多個引擎中進行測量。如果僅用 Chrome 進行測量,你可能會得出:
forEach
相對于
for
而言還算不錯這樣的結論。
限制你的 CPU
請注意,你的開發機器通常比浏覽你網站的普通手機要快得多。
浏覽器具有一項功能,可讓你限制 CPU 性能。這樣的話,10 或 50 毫秒很快就會變成500毫秒。
衡量相對表現
實際上這些結果不僅取決于你的硬體,還取決于你的 CPU 和目前 JavaScript 線程的負載。嘗試在不同情況下進行測量,因為下次你重新啟動計算機時,你得到的數字看起來可能會大不相同。
結論
在本文中,我們看到了一些 JavaScript API,可以使用它們來衡量性能,以及如何在“真實世界”中使用它們。對于簡單的測量,我發現用
console.time
更容易。
我覺得很多前端開發人員普遍沒有對性能進行足夠的考慮,即使這對你的收入有直接的影響。
原文連結
https://felixgerschau.com/measuring-the-performance-of-java-script-functions