測量JavaScript函數的性能的簡單方法及與其餘方式對比

測量執行一個函數所需的時間老是一個很好的辦法,證實某些實現比另外一個實現的性能更好。這也是一個很好的方法,能夠確保性能沒有在某些改變後受到影響,也能夠追蹤瓶頸。javascript

良好的性能有助於得到良好的用戶體驗,良好的用戶體驗會讓用戶回頭客。一項研究顯示,88%的在線消費者由於性能問題,在用戶體驗不佳後用戶回來的可能性較小。前端

這就是爲何可以識別代碼中的瓶頸並測量改進的緣由。尤爲是在爲瀏覽器開發JavaScript時,要注意到你寫的每一行JavaScript都有可能阻塞DOM,由於它是一種單線程語言。java

在這篇文章中,我將解釋你如何測量你的功能的性能,以及如何處理你從它們中獲得的結果。api

Perfomance.now

performance API經過其功能 performance.now() 提供對 DOMHighResTimeStamp 的訪問,該函數返回自頁面加載以來通過的時間(以毫秒爲單位),精度最高爲5µs(以分數爲單位)。數組

因此在實踐中,你須要取兩個時間戳,保存在一個變量中,而後讓第二個時間戳減去第一個時間戳。瀏覽器

const t0 = performance.now();
for (let i = 0; i < array.length; i++) {
  // some code
}
const t1 = performance.now();
console.log(t1 - t0, 'milliseconds');
複製代碼

Chrome輸出性能優化

0.6350000001020817 "milliseconds"
複製代碼

Firefox輸出bash

1 milliseconds
複製代碼

在這裏,咱們能夠看到Firefox中的結果與Chrome徹底不一樣,這是由於Firefox版本從60開始將 performance API 的精度下降到2ms。微信

performance API提供的功能遠比只返回時間戳要多得多,它可以測量導航計時、用戶計時或資源計時。請看這篇文章,裏面有更詳細的解釋。dom

可是,對於咱們的用例,咱們只想測量單個函數的性能,所以時間戳就足夠了。

那不是和Date.now同樣嗎?

如今你可能會想:我也能夠用 Date.now 來作這個啊。

是的,能夠,可是有缺點。

Date.now 以毫秒爲單位返回從Unix紀元("1970-01-01-01T00:00:00:00Z")開始的時間,而且取決於系統時鐘。這不只意味着它沒有那麼精確,並且也不必定會遞增。WebKit工程師(Tony Gentilcore)的解釋以下:

也許較少考慮到的是,基於系統時間的Date也不是真正的用戶監控的理想選擇。大多數系統都會運行一個守護進程來按期同步時間。一般狀況下,時鐘每隔15-20分鐘就會調整幾毫秒。在這個速度下,大約有1%的10秒的時間間隔是不許確的。

Console.time

該API確實易於使用,只需將 console.time 放在你要測量的代碼前面,將 console.timeEnd 放在要測量的代碼後面,便可使用相同的 string 參數調用該函數,一頁上最多能夠同時使用10,000個計時器。

精度與 performance API 相同,但這又取決於瀏覽器。

console.time('test');
for (let i = 0; i < array.length; i++) {
  // some code
}
console.timeEnd('test');
複製代碼

這樣會自動生成易於理解的輸出,以下所示:

Chrome輸出

test: 0.766845703125ms
複製代碼

Firefox輸出

test: 2ms - timer ended
複製代碼

這裏的輸出又與Performance API很是類似。

console.time 的優勢是易於使用,由於它不須要手動計算兩個時間戳之間的差。

縮短期精度

若是你在不一樣的瀏覽器中使用上面提到的API來測量你的函數,你可能會發現結果會有差別

這是因爲瀏覽器試圖保護用戶免受定時攻擊指紋攻擊, 若是時間戳太準確,黑客可使用它來識別用戶。

例如,Firefox之類的瀏覽器試圖經過將精度下降到2ms(版本60)來防止這種狀況。

須要注意的事項

如今,你已經擁有測量JavaScript函數的速度所需的工具。可是,最好避免一些陷阱。

分而治之

你注意到在過濾一些結果時有些東西很慢,可是你不知道瓶頸在哪裏。

與其胡亂猜想代碼中哪一部分是慢的,不如用上述這些函數來測量。

要追蹤它,首先把你的 console.time 語句放在慢的代碼塊周圍。而後測量它們的不一樣部分是如何執行的,若是其中一個部分比其餘部分慢,那麼就繼續下去,每次深刻到那裏,直到找到瓶頸。

這些語句之間的代碼越少,跟蹤不感興趣的內容的可能性就越小。

注意輸入值

在實際應用中,給定函數的輸入值可能會發生很大變化。僅針對任意隨機值測量函數的速度並不能提供咱們能夠實際使用的任何有價值的數據。

確保使用相同的輸入值運行代碼。

屢次運行函數

假設你有一個函數對一個數組進行迭代,對每一個數組的值進行一些計算,並返回一個數組的結果。你想知道是forEach 仍是簡單的 for 循環更有效。

這是函數:

function testForEach(x) {
  console.time('test-forEach');
  const res = [];
  x.forEach((value, index) => {
    res.push(value / 1.2 * 0.1);
  });

  console.timeEnd('test-forEach')
  return res;
}

function testFor(x) {
  console.time('test-for');
  const res = [];
  for (let i = 0; i < x.length; i ++) {
    res.push(x[i] / 1.2 * 0.1);
  }

  console.timeEnd('test-for')
  return res;
}
複製代碼

你能夠這樣測試它們:

const x = new Array(100000).fill(Math.random());
testForEach(x);
testFor(x);
複製代碼

若是你在Firefox中運行上述函數,你將得到相似如下的輸出:

test-forEach: 27ms - timer ended
test-for: 3ms - timer ended
複製代碼

看起來forEach變慢了,對吧?

讓咱們看看是否使用相同的輸入兩次運行相同的函數:

testForEach(x);
testForEach(x);
testFor(x);
testFor(x);
複製代碼
test-forEach: 13ms - timer ended
test-forEach: 2ms - timer ended
test-for: 1ms - timer ended
test-for: 3ms - timer ended
複製代碼

若是咱們第二次調用 forEach 測試,它的性能與 for 循環同樣好。鑑於初始值較慢,可能不管如何都不值得使用 forEach

...在多個瀏覽器中

若是咱們在Chrome中運行上述代碼,結果會忽然看起來不一樣:

test-forEach: 6.156005859375ms
test-forEach: 8.01416015625ms
test-for: 4.371337890625ms
test-for: 4.31298828125ms
複製代碼

這是由於Chrome和Firefox具備不一樣的JavaScript引擎,而且具備不一樣類型的性能優化。意識到這些差別是一件好事。

在這種狀況下,Firefox在相同輸入的狀況下,對 forEach 的使用進行了較好的優化。

for 在兩個引擎上的性能都更好,所以最好堅持使用 for 循環。

這是爲何要在多個引擎中進行測量的一個很好的例子。若是僅使用Chrome進行測量,您可能會得出結論,與 for 相比,forEach 並不那麼糟糕。

節流你的CPU

這些數值看起來並不高。要知道,你的開發機器一般比你的網站所使用的普通手機瀏覽速度要快得多。

爲了感覺一下這個樣子,瀏覽器有一個功能,可讓你節流你的CPU性能。

有了這個,那些10或50ms很快就變成了500ms。

測量相對錶現

這些原始結果實際上不只僅取決於你的硬件,還取決於你的CPU和你的JavaScript線程的當前負載。儘可能關注你的測量結果的相對改進,由於下次重啓電腦時,這些數字可能會看起來很不同。

總結

在本文中,咱們看到了一些JavaScript API,咱們可使用它們來測量性能,以及如何在「真實世界」中使用它們。對於簡單的測量,我發現使用 console.time 更容易。

我以爲不少前端開發人員天天都沒有對性能進行足夠的考慮,即便這對收入有直接影響。


來源:dev.to,做者:Felix Gerschau

翻譯:《前端外文精選》微信公衆號

相關文章
相關標籤/搜索