天天看點

怎樣測試 JavaScript 的函數性能

通過衡量執行某個函數所花費的時間,以“證明”某些實作比另一些實作更高效始終是一個很好的主意。這也是確定性能在進行一些修改後不受影響并找出瓶頸的好方法。

良好的性能有助于獲得良好的使用者體驗。良好的使用者體驗能夠留住使用者。像此研究(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