測量JavaScript函數(shù)的性能的簡單方法及與其他方式對比
測量執(zhí)行一個函數(shù)所需的時間總是一個很好的辦法,證明某些實(shí)現(xiàn)比另一個實(shí)現(xiàn)的性能更好。這也是一個很好的方法,可以確保性能沒有在某些改變后受到影響,也可以追蹤瓶頸。
良好的性能有助于獲得良好的用戶體驗(yàn),良好的用戶體驗(yàn)會讓用戶回頭客。一項(xiàng)研究顯示,88%的在線消費(fèi)者因?yàn)樾阅軉栴},在用戶體驗(yàn)不佳后用戶回來的可能性較小。
這就是為什么能夠識別代碼中的瓶頸并測量改進(jìn)的原因。尤其是在為瀏覽器開發(fā)JavaScript時,要注意到你寫的每一行JavaScript都有可能阻塞DOM,因?yàn)樗且环N單線程語言。
在這篇文章中,我將解釋你如何測量你的功能的性能,以及如何處理你從它們中得到的結(jié)果。
Perfomance.now
performance API通過其功能 performance.now() 提供對 DOMHighResTimeStamp 的訪問,該函數(shù)返回自頁面加載以來經(jīng)過的時間(以毫秒為單位),精度最高為5µs(以分?jǐn)?shù)為單位)。
所以在實(shí)踐中,你需要取兩個時間戳,保存在一個變量中,然后讓第二個時間戳減去第一個時間戳。
- 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輸出
- 1 milliseconds
在這里,我們可以看到Firefox中的結(jié)果與Chrome完全不同,這是因?yàn)镕irefox版本從60開始將 performance API 的精度降低到2ms。
performance API提供的功能遠(yuǎn)比只返回時間戳要多得多,它能夠測量導(dǎo)航計(jì)時、用戶計(jì)時或資源計(jì)時。請看這篇文章,里面有更詳細(xì)的解釋。
但是,對于我們的用例,我們只想測量單個函數(shù)的性能,因此時間戳就足夠了。
那不是和Date.now一樣嗎?
現(xiàn)在你可能會想:我也可以用 Date.now 來做這個啊。
是的,可以,但是有缺點(diǎn)。
Date.now 以毫秒為單位返回從Unix紀(jì)元("1970-01-01-01T00:00:00:00Z")開始的時間,并且取決于系統(tǒng)時鐘。這不僅意味著它沒有那么精確,而且也不一定會遞增。WebKit工程師(Tony Gentilcore)的解釋如下:
也許較少考慮到的是,基于系統(tǒng)時間的Date也不是真正的用戶監(jiān)控的理想選擇。大多數(shù)系統(tǒng)都會運(yùn)行一個守護(hù)進(jìn)程來定期同步時間。通常情況下,時鐘每隔15-20分鐘就會調(diào)整幾毫秒。在這個速度下,大約有1%的10秒的時間間隔是不準(zhǔn)確的。 |
Console.time
該API確實(shí)易于使用,只需將 console.time 放在你要測量的代碼前面,將 console.timeEnd 放在要測量的代碼后面,即可使用相同的 string 參數(shù)調(diào)用該函數(shù),一頁上最多可以同時使用10,000個計(jì)時器。
精度與 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 的優(yōu)點(diǎn)是易于使用,因?yàn)樗恍枰謩佑?jì)算兩個時間戳之間的差。
縮短時間精度
如果你在不同的瀏覽器中使用上面提到的API來測量你的函數(shù),你可能會發(fā)現(xiàn)結(jié)果會有差異。
這是由于瀏覽器試圖保護(hù)用戶免受定時攻擊和指紋攻擊, 如果時間戳太準(zhǔn)確,黑客可以使用它來識別用戶。
例如,F(xiàn)irefox之類的瀏覽器試圖通過將精度降低到2ms(版本60)來防止這種情況。
需要注意的事項(xiàng)
現(xiàn)在,你已經(jīng)擁有測量JavaScript函數(shù)的速度所需的工具。但是,最好避免一些陷阱。
1. 分而治之
你注意到在過濾一些結(jié)果時有些東西很慢,但是你不知道瓶頸在哪里。
與其胡亂猜測代碼中哪一部分是慢的,不如用上述這些函數(shù)來測量。
要追蹤它,首先把你的 console.time 語句放在慢的代碼塊周圍。然后測量它們的不同部分是如何執(zhí)行的,如果其中一個部分比其他部分慢,那么就繼續(xù)下去,每次深入到那里,直到找到瓶頸。
這些語句之間的代碼越少,跟蹤不感興趣的內(nèi)容的可能性就越小。
2. 注意輸入值
在實(shí)際應(yīng)用中,給定函數(shù)的輸入值可能會發(fā)生很大變化。僅針對任意隨機(jī)值測量函數(shù)的速度并不能提供我們可以實(shí)際使用的任何有價值的數(shù)據(jù)。
確保使用相同的輸入值運(yùn)行代碼。
3. 多次運(yùn)行函數(shù)
假設(shè)你有一個函數(shù)對一個數(shù)組進(jìn)行迭代,對每個數(shù)組的值進(jìn)行一些計(jì)算,并返回一個數(shù)組的結(jié)果。你想知道是forEach 還是簡單的 for 循環(huán)更有效。
這是函數(shù):
- 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中運(yùn)行上述函數(shù),你將獲得類似以下的輸出:
- test-forEach: 27ms - timer ended
- test-for: 3ms - timer ended
看起來forEach變慢了,對吧?
讓我們看看是否使用相同的輸入兩次運(yùn)行相同的函數(shù):
- 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
如果我們第二次調(diào)用 forEach 測試,它的性能與 for 循環(huán)一樣好。鑒于初始值較慢,可能無論如何都不值得使用 forEach。
4. ...在多個瀏覽器中
如果我們在Chrome中運(yùn)行上述代碼,結(jié)果會突然看起來不同:
- test-forEach: 6.156005859375ms
- test-forEach: 8.01416015625ms
- test-for: 4.371337890625ms
- test-for: 4.31298828125ms
這是因?yàn)镃hrome和Firefox具有不同的JavaScript引擎,并且具有不同類型的性能優(yōu)化。意識到這些差異是一件好事。
在這種情況下,F(xiàn)irefox在相同輸入的情況下,對 forEach 的使用進(jìn)行了較好的優(yōu)化。
for 在兩個引擎上的性能都更好,因此最好堅(jiān)持使用 for 循環(huán)。
這是為什么要在多個引擎中進(jìn)行測量的一個很好的例子。如果僅使用Chrome進(jìn)行測量,您可能會得出結(jié)論,與 for 相比,forEach 并不那么糟糕。
5. 節(jié)流你的CPU
這些數(shù)值看起來并不高。要知道,你的開發(fā)機(jī)器通常比你的網(wǎng)站所使用的普通手機(jī)瀏覽速度要快得多。
為了感受一下這個樣子,瀏覽器有一個功能,可以讓你節(jié)流你的CPU性能。
有了這個,那些10或50ms很快就變成了500ms。
6. 測量相對表現(xiàn)
這些原始結(jié)果實(shí)際上不僅僅取決于你的硬件,還取決于你的CPU和你的JavaScript線程的當(dāng)前負(fù)載。盡量關(guān)注你的測量結(jié)果的相對改進(jìn),因?yàn)橄麓沃貑㈦娔X時,這些數(shù)字可能會看起來很不一樣。
總結(jié)
在本文中,我們看到了一些JavaScript API,我們可以使用它們來測量性能,以及如何在“真實(shí)世界”中使用它們。對于簡單的測量,我發(fā)現(xiàn)使用 console.time 更容易。