怎么使用Performance監(jiān)控前端性能

performance.now

Performance是一個(gè)做前端性能監(jiān)控離不開的API,最好在頁(yè)面完全加載完成之后再使用,因?yàn)楹芏嘀当仨氃陧?yè)面完全加載之后才能得到。最簡(jiǎn)單的辦法是在window.onload事件中讀取各種數(shù)據(jù)。

performance.now()方法返回一個(gè)精確到毫秒的 DOMHighRestimestamp

根據(jù) MDN :

這個(gè)時(shí)間戳實(shí)際上并不是高精度的。為了降低像Spectre這樣的安全威脅,各類瀏覽器對(duì)該類型的值做了不同程度上的四舍五入處理。(firefox從Firefox ?59開始四舍五入到2毫秒精度)一些瀏覽器還可能對(duì)這個(gè)值作稍微的隨機(jī)化處理。這個(gè)值的精度在未來的版本中可能會(huì)再次改善;瀏覽器開發(fā)者還在調(diào)查這些時(shí)間測(cè)定攻擊和如何更好的緩解這些攻擊。

立即學(xué)習(xí)前端免費(fèi)學(xué)習(xí)筆記(深入)”;

因?yàn)椋?jì)算一個(gè)函數(shù)的執(zhí)行時(shí)間,分別比較函數(shù)執(zhí)行前和執(zhí)行后的兩次 performance.now()的值即可,如下所示:

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

怎么使用Performance監(jiān)控前端性能

這里可以觀察到 Firefox 和 chrome 所呈現(xiàn)的結(jié)果完全不同。這是因?yàn)閺陌姹?0開始,F(xiàn)irefox 將performance ?API的精度降低到2ms。

performance API 不當(dāng)當(dāng)只有返回時(shí)間戳這個(gè)功能,還有很多實(shí)用方法,大家可以根據(jù)需要到 MDN 查詢相關(guān)的文檔。

然而,對(duì)于我們的用例,我們只想計(jì)算單個(gè)函數(shù)的性能,因此時(shí)間戳就足夠了。

performance.now() 和 date.now一樣嗎?

你可能會(huì)想,嘿,我也可以使用Date.now來做?

是的,你可以,但這有缺點(diǎn)。

Date.now返回自unix紀(jì)元(1970-01-01T00:00:00Z)以來經(jīng)過的時(shí)間(以毫秒為單位),并取決于系統(tǒng)時(shí)鐘。這不僅意味著它不夠精確,而且還不總是遞增。在此,webkit工程師Tony Gentilcore做出了解釋:

使用系統(tǒng)時(shí)間作為日期可能不是最好的選擇,也不適用于用戶監(jiān)控。大多數(shù)系統(tǒng)運(yùn)行一個(gè)守護(hù)程序,該守護(hù)程序定期同步時(shí)間。通常每15至20分鐘將時(shí)鐘調(diào)整幾毫秒。以該速率,大約10秒間隔的1%將是不準(zhǔn)確的。

Performance.mark 和 ?Performance.measure

除了Performance.now函數(shù)外,還有一些函數(shù)可以讓我們度量代碼不同部分的時(shí)間,并將它們作為性能測(cè)試工具(如Webpagetest)中的自定義度量。

Performance.mark

先來看看MDN中關(guān)于mark方法的定義:

  • The mark() method creates a timestamp in the browser’s performance entry ?buffer with the given name.

這段話可以分解出三個(gè)關(guān)鍵詞。首先timestamp,這里的timestamp指的是高精度時(shí)間戳(千分之一毫秒),其次是performance entry ?buffer。

performance entry buffer指的是存儲(chǔ)performance實(shí)例對(duì)象的區(qū)域,初始值為空。

最后就是given name,表示生成的每一個(gè)timestamp都有相應(yīng)的名稱。

所以這句話就可以理解成,在瀏覽器的performance entry ?buffer中,根據(jù)名稱生成高精度時(shí)間戳。也就是很多人說過的**“打點(diǎn)”**。

就像Performance.now一樣,此函數(shù)的精度分?jǐn)?shù)高達(dá)5μs。

performance.mark('name');

標(biāo)記 的 performance entry將具有以下屬性值:

  • entryType – 設(shè)置為 “mark”.

  • name – 設(shè)置為mark被創(chuàng)建時(shí)給出的 “name”

  • startTime – 設(shè)置為 mark() 方法被調(diào)用時(shí)的 timestamp 。

  • duration – 設(shè)置為 “0” (標(biāo)記沒有持續(xù)時(shí)間).

Performance.measure

同樣先來看看 MDN 上關(guān)于 measure 的定義:

這段定義和上面 mark 的定義有些類似,其最核心的不同點(diǎn)在于這句話 between two specified ?marks。所以measure是指定兩個(gè)mark點(diǎn)之間的時(shí)間戳。如果說mark可以理解為**”打點(diǎn)”的話,measure就可以理解為”連線”**。

performance.measure(name, startMark, endMark);

計(jì)算兩個(gè)mark之間的時(shí)長(zhǎng),創(chuàng)建一個(gè)DOMHighResTimeStamp保存在資源緩存數(shù)據(jù)中,可通過performance.getEntries()等相關(guān)接口獲取。

  • entryType 為字符串 measure

  • name 為創(chuàng)建時(shí)設(shè)置的值

  • startTime為調(diào)用 measure 時(shí)的時(shí)間

  • duration為兩個(gè) mark 之間的時(shí)長(zhǎng)

從導(dǎo)航開始測(cè)量

performance.measure('measure name');

導(dǎo)航開始到標(biāo)記

performance.measure('measure name', undefined, 'mark-2');

從標(biāo)記到標(biāo)記

performance.measure('measure name', 'mark-1', 'mark-2');

資源性能數(shù)據(jù)

從 performance entry buffer 獲取數(shù)據(jù)

在上面的函數(shù)中,總是提到結(jié)果存儲(chǔ)在performance entry buffer,但是如何訪問其中的內(nèi)容呢?

performance API有3個(gè)函數(shù)可以用來訪問該數(shù)據(jù):

performance.getEntries()

獲取一組當(dāng)前頁(yè)面已經(jīng)加載的資源PerformanceEntry對(duì)象。接收一個(gè)可選的參數(shù)options進(jìn)行過濾,options支持的屬性有name,entryType,initiatorType。

let entries = window.performance.getEntries();

performance.getEntriesByName

根據(jù)參數(shù)name,type獲取一組當(dāng)前頁(yè)面已經(jīng)加載的資源數(shù)據(jù)。資源數(shù)據(jù)中的”name”字段對(duì)應(yīng)于”name”的取值,資源數(shù)據(jù)中的”entryType”字段對(duì)應(yīng)于”type”的取值。

let entries = window.performance.getEntriesByName(name, type);

performance.getEntriesByType

根據(jù)參數(shù)type獲取一組當(dāng)前頁(yè)面已經(jīng)加載的資源數(shù)據(jù)。type取值對(duì)應(yīng)到資源數(shù)據(jù)中的entryType字段。

var entries = window.performance.getEntriesByType(type);

結(jié)合事例:

performance.mark('mark-1'); // some code performance.mark('mark-2') performance.measure('test', 'mark-1', 'mark-2') console.log(performance.getEntriesByName('test')[0].duration);

Console.time

這個(gè) ?API確實(shí)易于使用。當(dāng)需要統(tǒng)計(jì)一段代碼的執(zhí)行時(shí)間時(shí),可以使用console.time方法與console.timeEnd方法,其中console.time方法用于標(biāo)記開始時(shí)間,console.timeEnd方法用于標(biāo)記結(jié)束時(shí)間,并且將結(jié)束時(shí)間與開始時(shí)間之間經(jīng)過的毫秒數(shù)在控制臺(tái)中輸出。這兩個(gè)方法的使用方法如下所示。

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

怎么使用Performance監(jiān)控前端性能

輸出的結(jié)果與Performance API非常相似。

console.time的優(yōu)點(diǎn)是易于使用,因?yàn)樗恍枰謩?dòng)計(jì)算兩個(gè)時(shí)間戳之間的差。

減少時(shí)間精度

如果在不同的瀏覽器中使用上面提到的 api 測(cè)量函數(shù),你可能會(huì)注意到結(jié)果是不同的。

這是由于瀏覽器試圖保護(hù)用戶免受時(shí)序攻擊(timing attack)和指紋采集(Fingerprinting ?),如果時(shí)間戳過于準(zhǔn)確,黑客可以使用它們來識(shí)別用戶。

例如,F(xiàn)irefox等瀏覽器試圖通過將精度降低到2ms(版本60)來防止這種情況發(fā)生。

注意事項(xiàng)

現(xiàn)在,我們已經(jīng)知道了要測(cè)量JavaScript函數(shù)的速度所需方法。但是,最好還要避免一些陷阱:

分而治之

開發(fā)過程中,我們可能會(huì)我發(fā)現(xiàn)有些模塊執(zhí)行速度很慢,但是我們不知道具體問題出在哪里。一種解決方案是使用前面提到的這些函數(shù)來測(cè)量代碼,而不是隨便猜測(cè)哪一部分比較慢。

為了跟蹤它,你需要在執(zhí)行速度較慢的代碼塊周圍放置console.time語(yǔ)句。然后測(cè)量它們不同部分的表現(xiàn)。如果一個(gè)比另一個(gè)慢,那就繼續(xù)往下走,直到發(fā)現(xiàn)問題所在。

注意輸入值

在實(shí)際應(yīng)用中,給定函數(shù)的輸入值可能會(huì)發(fā)生很大變化。我們無(wú)法通過僅針對(duì)任意隨機(jī)值測(cè)量函數(shù)的速度來獲得任何實(shí)用的有價(jià)值數(shù)據(jù)。

確保使用相同的輸入值運(yùn)行代碼。

多次運(yùn)行該函數(shù)

如果你擁有一個(gè)函數(shù),它的功能在于遍歷一個(gè)數(shù)組,在對(duì)數(shù)組的每個(gè)值執(zhí)行一些計(jì)算后,返回一個(gè)包含計(jì)算結(jié)果的新數(shù)組。你想知道是foreach循環(huán)還是簡(jiǎn)單的for循環(huán)性能更好。

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; }

然后這樣測(cè)試它們:

const x = new Array(100000).fill(Math.random()); testForEach(x); testFor(x);

如果在 Firefox 中運(yùn)行上述函數(shù),結(jié)果:

怎么使用Performance監(jiān)控前端性能

看起來forEach慢多了,對(duì)吧?

那如果是相同的輸入,運(yùn)行兩次呢:

testForEach(x); testForEach(x); testFor(x); testFor(x);

怎么使用Performance監(jiān)控前端性能

在第二次調(diào)用forEach的情況下,其執(zhí)行效果應(yīng)該是和使用for循環(huán)相同。考慮到初始值較慢,在一些性能要求極高的項(xiàng)目,可能就不適合使用forEach。

在多個(gè)瀏覽器中測(cè)試

如果我們?cè)贑hrome中運(yùn)行上述代碼,結(jié)果又會(huì)不一樣:

怎么使用Performance監(jiān)控前端性能

這是因?yàn)镃hrome和Firefox具有不同的JavaScript引擎,它們具有不同類型的性能優(yōu)化

在本例中,F(xiàn)irefox 在對(duì)相同輸入的forEach進(jìn)行優(yōu)化方面做得更好。

for在兩個(gè)引擎上的性能都更好,因此在一些性能要求極高的項(xiàng)目就需要使用for循環(huán)。

這是為什么要在多個(gè)引擎中進(jìn)行測(cè)量的一個(gè)很好的例子。只使用Chrome作為度量標(biāo)準(zhǔn)可能導(dǎo)致你得出結(jié)論,forEach與for相比并不那么糟糕。

限制的 CPU

在本地測(cè)試時(shí)得到的結(jié)果不代表用戶在瀏覽器中的使用情況,因?yàn)槲覀冮_發(fā)者使用的電腦通常比大多數(shù)用戶的電腦配置更好。

瀏覽器有一個(gè)特性可以限制CPU性能,我們通過設(shè)置可以更貼切一些真實(shí)情況。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊9 分享