本文翻譯自:https://nolanlawson.com/2021/02/23/javascript-performance-beyond-bundle-size/
出處:https://juejin.cn/post/6935306384724459551
有 一個古老的故事 ,關於一個醉漢試圖在路燈下找到他的鑰匙。為什麼?因為那是最明亮的地方。這是一個有趣的故事,但也是有關聯的,因為作為人類,我們都傾向於走阻力最小的道路。
我認為我們在網路效能社群也有同樣的問題。最近人們非常關注 JavaScript bundle 大小:你的依賴有多大?你能使用一個更小的嗎?你能懶惰載入嗎?但是我相信我們首先關注 bundle 大小,因為它很容易測量。
這並不是說包的大小不重要!就像你 可能 把鑰匙留在路燈上一樣。見鬼,你不妨先檢查一下那裡,因為那裡是最快的地方。但是這裡還有一些其他的東西很難測量,但也同樣重要:
Parse/compile時間執行時間電力使用記憶體使用磁碟使用JavaScript依賴性會影響所有這些指標。但是它們比 bundle 大小討論得少,我懷疑這是因為它們不太容易測量。在這篇文章中,我想談談我如何處理bundle大小,以及我如何處理其他指標。
束大小當談到JavaScript程式碼的大小時,你必須精確。有些人會說“我的庫是10千位元組。”那是縮小的嗎? Tree-shaken?你使用了最高的 Gzip 設定(9)嗎? Brotli壓縮呢?
這聽起來很頭疼,但區別實際上很重要,尤其是壓縮和未壓縮大小之間的區別。壓縮大小影響透過電線傳送位元組的速度,而未壓縮大小影響瀏覽器解析、編譯和執行JavaScript所需的時間。(這些往往與程式碼大小相關,儘管它不是一個完美的預測器。)
然而,最重要的是保持一致。你不想用未縮小、未壓縮的大小來衡量庫A,而用縮小和壓縮的大小來衡量庫B(除非你為它們提供的服務有真正的不同)。
捆綁恐懼症對我來說, 捆綁恐懼症 是捆綁大小分析的瑞士軍刀。你可以從npm中查詢任何依賴項,它會告訴你縮小的大小(瀏覽器解析和執行的內容)以及縮小和壓縮的大小(瀏覽器下載的內容)。
例如,我們可以使用這個工具看到 [react-dom](https://bundlephobia.com/result? [email protected]) 的重量縮小了121.1kB,但是 [preact](https://bundlephobia.com/result? [email protected]) 重10.2kB。所以我們可以確認Preact 真的是 誠實的商品——一個很小的反應相容框架!
在這種情況下,我不會糾結於到底是哪個迷你程式或者到底是什麼Gzip壓縮級別的捆綁恐懼症,因為至少它在任何地方都使用相同的系統。所以我知道我在比較蘋果和蘋果。
話雖如此,邦德恐懼症有一些警告:
它沒有告訴你樹形調整的成本。如果你只匯入一個模組的一部分,其他部分可能會被樹形調整掉。它不會告訴你子目錄依賴關係。例如,我知道 import 'preact' 有多貴,但是 import 'preact/compat' 可以是任何東西—— compat.js 可能是一個巨大的檔案,我無法知道。如果涉及到多填充(例如,您的bundler為Node的 Buffer API或JavaScript Object.assign() API注入了多填充),您不一定會在這裡看到它。在上述所有情況下,您只需要執行bundler並檢查輸出。每個bundler都是不同的,根據配置或其他因素,您可能最終會得到一個大的bundler或一個小的bundler。所以接下來,讓我們繼續討論特定於bundler的工具。
WebpackBundle分析儀我喜歡 WebpackBundle Analyzer 。它提供了Webpack輸出中每個塊的良好視覺化,以及這些塊中的哪些模組。
就 它顯示的大小 而言,最有用的兩個是“解析”(預設值)和“Gzip." "解析”本質上意味著“縮小”,所以這兩個測量值與捆綁恐懼症告訴我們的大致相當。但這裡的區別是,我們實際上是在執行捆綁程式,所以我們知道這些大小對於我們特定的應用程式是準確的。
Rollup外掛分析儀對於Rollup,我真的很想有一個像WebpackBundle Analyzer這樣的圖形介面。但是我發現的下一個最好的東西是 Rollup外掛分析器 ,它會在構建時將您的模組大小輸出到控制檯。
不幸的是,這個工具沒有給我們縮小或壓縮的大小——只是在這種最佳化發生之前 由Rollup看到 的大小。它不完美,但在緊要關頭它很棒。
其他捆綁大小的工具我曾涉足並發現有用的其他工具:
捆綁大小捆綁好友資源地圖資源管理器網路包分析我相信你可以找到其他工具來新增到這個列表中!
超越捆綁正如我提到的,我不認為JavaScript bundle大小就是一切。作為第一近似值,它很棒,因為它(相對)容易測量,但是有很多其他指標可以影響頁面效能。
執行時CPU成本第一個也是最重要的一個是執行時成本。這可以分成幾個桶:
解析彙編,彙編行刑這三個階段基本上是呼叫 require("some-dependency") 或 import "some-dependency" 的端到端成本。它們可能與捆綁大小相關,但不是一對一的對映。
舉個簡單的例子,這裡有一個(微小的!)消耗大量CPU的JavaScript程式碼段:
const start=Date. now ()while (Date.now() - start < 5000) {}複製程式碼
這個程式碼段在Bundlephessa上會獲得很高的分數,但不幸的是,它會阻塞主執行緒5秒鐘。這是一個有點荒謬的例子,但在現實世界中,您可以找到儘管如此錘擊主執行緒的小庫。遍歷DOM中的所有元素,在LocalStore中迭代一個大陣列,計算pi的數字... 除非你親自檢查了你所有的依賴,否則很難知道他們在裡面做什麼。
解析和編譯都很難衡量。很容易欺騙自己,因為瀏覽器對 位元組碼快取 有很多最佳化。例如,瀏覽器可能不會在第二頁載入或第三頁載入時執行parse/compile 第三頁載入 時執行步驟 (!), 或者JavaScript快取在Service Worker中。所以你可能會認為一個模組parse/compile便宜,而瀏覽器只是提前快取了它。
ChromeDevTools中的編譯和執行。請注意,Chrome 在主執行緒之外進行 一些解析和編譯。
100%安全的唯一方法是完全清除瀏覽器快取並測量第一頁載入。我不喜歡胡鬧,所以通常我會在private/guest瀏覽視窗或完全獨立的瀏覽器中這樣做。您還需要確保禁用任何瀏覽器擴充套件(私有模式通常會這樣做),因為這些擴充套件會影響頁面載入時間。您不想在分析Chrome跟蹤的中途意識到您正在測量您的密碼管理器!
我通常做的另一件事是將Chrome的中央處理器節流設定為4倍或6倍。我認為4x“足夠類似於移動裝置”,6x是“一臺超級騙子減慢速度的機器,它使痕跡更容易閱讀,因為一切都更大。”使用你想要的任何一個;兩者都比你(可能)的高階開發者機器更能代表真實使用者。
如果我擔心網路速度,這也是我開啟網路節流的地方。“快速3G”通常是一個很好的選擇,它在“更像現實世界”和“不慢到我開始對電腦大喊大叫”之間找到了最佳位置
所以綜合起來,我獲得準確跟蹤的步驟通常是:
開啟private/guest瀏覽視窗。如有必要,導航到 about:blank (您不想測量瀏覽器主頁的 unload 事件)。在Chrome中開啟DevTools。轉到效能選項卡。在設定中,開啟CPU節流and/or網路節流。單擊記錄按鈕。鍵入URL並按回車鍵。載入頁面後停止記錄。現在您有了一個性能跟蹤(也稱為“時間線”或“配置檔案"), ,它將向您顯示JavaScript程式碼在初始頁面載入中的parse/compile/execution時間。不幸的是,這部分最終可能會非常手動,但有一些技巧可以讓它變得更容易。
最重要的是,使用 使用者計時應用程式設計介面 (又稱效能標記和度量)將網路應用程式的部分標記為對您有意義的名稱。專注於您擔心會昂貴的部分,例如根應用程式的初始渲染、阻塞XHR呼叫或引導您的狀態物件。
如果您擔心這些API的(小)開銷,您可以在生產中剔除效能 performance.mark / performance.measure 測量呼叫。我喜歡 根據查詢字串引數 開啟或關閉它,這樣如果我想分析生產構建,我可以很容易地開啟生產中的使用者計時。Terser的 [pure_funcs 選項]( terser.org/docs/api-re… 用來在縮小時刪除 performance.mark 和 performance.measure 呼叫。(見鬼,你也可以刪除 console.log 。非常方便。)
另一個有用的工具是 [mark-loader](https://github.com/statianzo/mark-loader) ,它是一個Webpack外掛,可以自動將您的模組包裝在mark/measure呼叫中,這樣您就可以看到每個依賴項的執行時成本。為什麼要在JavaScript呼叫堆疊上費解,因為該工具可以準確地告訴您哪些依賴項消耗了多少時間?
在生產模式下載入三個. js、時刻和反應。如果沒有使用者計時,你能找出時間花在哪裡嗎?
在測量執行時效能時需要注意的一件事是,成本在精簡和未精簡的程式碼之間可能會有所不同。未使用的函式可能會被剝離,程式碼會更小、更最佳化,庫可能會定義不在生產模式下執行的 process.env. NODE_ENV === 'development' 塊。
我處理這種情況的一般策略是將縮小的生產構建視為真理的來源,並使用標記和度量來使其易於理解。但是,如前所述, performance.mark 和 performance.measure 有自己的小開銷,因此您可能希望使用查詢字串引數來切換它們。
電力使用你不必是一個環保主義者,就能認為最大限度地減少電力使用是重要的。我們生活在一個越來越多的人用不插電源插座的裝置瀏覽網路的世界裡,他們最不想看到的就是因為一個行為不端的網站而耗盡電力。
我傾向於認為電源使用是CPU使用的一個子集。這有一些例外,比如 喚醒收音機來進行網路連線 ,但大多數時候,如果一個網站消耗過多的電源,那是因為它在主執行緒上消耗了過多的CPU。
因此,我上面所說的關於改進JavaScriptparse/compile/execute時間的一切也將降低功耗。但是對於長壽命的網路應用程式來說,最陰險的耗電形式是在第一頁載入之後。這可能表現為使用者突然注意到他們的膝上型電腦風扇在嗡嗡作響,或者他們的手機越來越熱,即使他們只是在看一個(顯然)空閒的網頁。
同樣,在這種情況下,首選的工具是Chrome開發工具效能選項卡,使用的基本上與上述步驟相同。然而,您需要尋找的是重複的CPU使用,通常是由於計時器或動畫。例如,編碼不佳的自定義捲軸、 Intersection觀測器 多填充或動畫載入旋轉器可能會決定它們需要在每個 requestAnimationFrame 或 setInterval 迴圈中執行程式碼。
一個表現不佳的JavaScript小部件。注意JavaScript使用的小高峰,即使頁面空閒,它也顯示出持續的CPU使用。
請注意,由於未最佳化的CSS動畫,這種功耗也可能發生——不需要JavaScript!(在這種情況下,Chrome使用者介面中的峰值將是紫色的,而不是黃色的。)對於長期執行的CSS動畫,請確保始終首選 GPU加速的 CSS屬性。
您可以使用的另一個工具是Chrome的 效能監視器 選項卡,它實際上不同於效能選項卡。我認為這是一種心跳監視器,可以顯示您的網站在效能方面的表現,而無需手動啟動和停止跟蹤。如果您在一個惰性的網頁上看到持續的CPU使用,那麼您可能會遇到電源使用問題。
效能監視器中同樣表現不佳的JavaScript小部件。注意CPU使用的持續低嗡嗡聲,以及記憶體使用中的鋸齒模式,表明記憶體不斷被分配和取消分配。
另外:向WebKit的人致敬,他們為Safari Web檢查員添加了一個明確的 能源影響 面板。另一個值得一看的好工具!
記憶體使用記憶體使用曾經是一個很難分析的東西,但是工具最近有了很大的改進。
去年我已經寫 了一篇關於記憶體洩漏 的文章,但是重要的是要記住記憶體 使用 和記憶體 洩漏 是兩個獨立的問題。一個網站可以在沒有明確洩露記憶體的情況下擁有高記憶體使用率。而另一個網站可能從小處開始,但最終會因為失控的洩漏而膨脹到巨大的規模。
您可以閱讀上面的部落格文章,瞭解如何分析記憶體洩漏。但是就記憶體使用而言,我們有了一個新的瀏覽器應用程式設計介面,它在測量記憶體方面有很大幫助: [performance.measureUserAgentSpecificMemory](https://www.chromestatus.com/feature/5685965186138112) (以前的 performance.measureMemory ,遺憾的是,它少得多)。這個應用程式設計介面有幾個優點:
它返回一個承諾,在垃圾收集後自動解析。(不再需要 奇怪的駭客 來強制GC!)它測量的不僅僅是JavaScript VM大小,它還包括DOM記憶體以及網路工作人員和iframe中的記憶體。在跨源框架的情況下,由於 站點隔離 而被過程隔離,它將分解屬性。所以你可以確切地知道你的廣告和嵌入有多需要記憶體!以下是API的示例輸出:
{ "breakdown": [ { "attribution": ["https://pinafore.social/"], "bytes": 755360, "types": ["Window", "JS"] }, { "attribution": [], "bytes": 804322, "types": ["Window", "JS", "Shared"] } ], "bytes": 1559682}複製程式碼
在這種情況下, bytes 是您要用於“我使用了多少記憶體”的橫幅指標 breakdown 是可選的,規範明確指出 瀏覽器可以決定不包括它 。
也就是說,使用這個應用程式設計介面仍然是很挑剔的。首先,它只在Chrome89中可用+. (在稍舊的版本中,您可以設定“啟用實驗網路平臺功能”標誌並使用舊的 performance.measureMemory 。)然而,更成問題的是,由於濫用的可能性,該應用程式設計介面僅限於 跨源隔離的上下文 。這實際上意味著您必須設定一些 特殊標題 ,如果您依賴任何跨源資源(外部CSS、JavaScript、影象等.), 他們也需要設定一些特殊標題。
不過,如果這聽起來太麻煩了,並且如果您只計劃將此應用程式設計介面用於自動測試,那麼您可以使用禁用-網路安全標誌](禁用-網路安全標誌)執行Chrome [--disable-web-security 標誌]( stackoverflow.com/a/58658101/… 執行 . (當然,風險自負!)不過,請注意,測量記憶體目前 並不在無頭模式下工作 。
當然,這個應用程式設計介面也沒有給你很大的粒度。例如,你無法弄清楚反應占用了X個位元組,Lodash佔用了Y個位元組,等等。A/B測試可能是解決這種問題的唯一有效方法。但這仍然比我們用來測量記憶體的舊工具好得多(它有如此多的缺陷,甚至不值得描述)。
磁碟使用限制磁碟使用在Web應用程式場景中是最重要的,在該場景中,可以根據裝置上的可用儲存量達到瀏覽器 配額限制 。過度的儲存使用可以有多種形式,例如將太多的 大型映像 填充到 ServiceWorker快取 中,但是JavaScript也可以累加。
你可能會認為JavaScript模組的磁碟使用與其bundle大小(即快取它的成本)直接相關,但也有一些情況是不正確的。例如,對於我自己的 [emoji-picker-element](https://github.com/nolanlawson/emoji-picker-element) ,我大量使用 索引資料庫 來儲存表情資料。這意味著我必須瞭解與資料庫相關的磁碟使用情況,例如儲存不必要的資料或建立過多的索引。
Chrome開發工具有一個 “應用程式”選項卡 ,顯示網站的總儲存使用量。作為第一個近似值,這很好,但是我發現這個螢幕可能有點不一致,資料也必須手動收集。此外,我感興趣的不僅僅是Chrome,因為索引資料庫在瀏覽器之間的實現有很大的不同,所以儲存大小可能會有很大的不同。
我找到的解決方案是一個啟動Playwright的 小指令碼 Playwright 的小指令碼,這是一個類似 Puppeteer 的工具,它的優點是能夠啟動更多的瀏覽器,而不僅僅是Chrome。另一個巧妙的功能是它可以啟動帶有新儲存區域的瀏覽器,所以你可以啟動一個瀏覽器,將儲存寫入 /tmp ,然後測量每個瀏覽器的IndexedDB使用情況。
舉個例子,以下是我對當前版本 emoji-picker-element 看法:
無標題的
當然,如果您想測量ServiceWorker快取、 LocalStore 等的儲存大小,則必須修改此指令碼。
另一個在生產環境中可能更好的選擇是 [StorageManager.estimate()](https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/estimate) 應用程式設計介面。然而,這更多的是為了確定你是否接近配額限制,而不是效能分析,所以我不確定它作為磁碟使用指標的準確性。正如MDN指出的:“返回的值不精確;出於安全原因,在壓縮、重複資料刪除和模糊處理之間,它們將是不精確的。”
結論效能是一個多方面的事情。如果我們能把它減少到一個單一的指標,比如bundle大小,那就太好了,但是如果你真的想涵蓋所有的基礎,有很多不同的角度需要考慮。
有時這會讓人感到難以承受,這就是為什麼我認為像 核心網路生命體徵 這樣的計劃,或者對bundle大小的普遍關注,並不是一件壞事。如果你告訴人們他們需要最佳化十幾個不同的指標,他們可能會決定不最佳化其中的任何一個。
如果有這樣的東西存在,那麼就更容易做出明智的決定,決定使用哪些JavaScript依賴項,是否懶惰載入它們,等等。但與此同時,有許多不同的方法來收集這些資料,我希望這篇部落格文章至少鼓勵你超越街燈。
出處:https://juejin.cn/post/6935306384724459551