編譯 | 明明如月 責編 | 阿哲
參考連結:https://itnext.io/we-rendered-a-million-web-pages-to-find-out-what-makes-the-web-slow-72bbba9ade96
我們渲染了全球排名前 100 萬個頁面,跟蹤我們能想到的所有性能指標,記錄每一個請求的 URL 和請求產生的錯誤。據我們所知,這可能是第一個將網頁性能、訪問錯誤和頁面腳本庫關聯在一起的數據集。我們將藉助這篇文章幫助大家了解如何構造高性能的網站。
訪問了 100 萬個網頁,每個頁面 65 個指標、請求了 2100 多萬個 URL,記錄了 38 萬 3 千個錯誤,記錄了 8 千 8 百多萬個全局變量。
如果你認為自己可以比我們分析的更好,可以從我們發布到 Kaggle 上的連結下載到我們的數據集自己動手分析。
為什麼要渲染一百多萬個網頁?
人們普遍認為現在網際網路比 15 年前更加緩慢而且存在的問題更多。
越來越多的 JavaScript 框架、Web 字體和 polyfill 抵消了更快的計算機性能、網絡和協議給我們帶來的提速。因此我們想通過實驗驗證是否真正如此,我們希望通過實驗找出導致 2020 年網站的緩慢和崩潰的共同原因。
這個計劃很簡單:編寫一個渲染排名前 100 萬的域名的根頁面 Web 瀏覽器的腳本,並記錄每一個可以想到的可能影響性能的因素:頁面渲染時間、請求計數、重繪、JavaScript 錯誤、使用的庫等等。
有了這些數據,我們就可以可以研究不同影響因素之間的關係。研究哪些因素最有助於降低頁面渲染時間?哪些類庫與可交互時間(TTI)相關?最常見的錯誤是什麼?
收集數據只需使用 puppeter 編寫 Chrome 腳本,在周末啟動 200 個 EC2 實例渲染 100 萬個網頁。
整體情況
圖:根 HTML 文檔的協議使用情況
實驗研究表明,HTTP 2 比 HTTP 1.1 使用得更加普遍,HTTP 3 使用量非常少。
圖:連結資源的協議情況
連結資源 HTTP3 協議的使用率是根 HTML 文檔的 100 倍。為什麼會是這樣?因為所有的站點都連結了一樣的內容:
圖:連結次數最多的 URL
那麼有些 JS 腳本被大部分網站引用,是不是就意味著瀏覽器會共享同一份緩存呢?其實並非如此:Chrome 86 之後,不同域名的請求不會共享一份緩存。火狐瀏覽器也計劃實現相同的功能。Safari 瀏覽器也早已經將不同的域名緩存分隔開來好多年了。
什麼讓網絡變慢:預測交互時間
如果我們能通過數據集和得到的加載時間分析出網頁變慢的原因就好了。我們將研究域交互度( dominteractive ),即文檔渲染到能夠讓用戶與之進行交互所需的時間。
我們查看每個指標與域交互的相關性。
圖:各種指標與域交互相關度
基本上每個指標都與域交互度呈現正相關的關係,除了 0.x 和 1.x 版本之外。這些指標中的許多指標之間也呈現正相關。我們需要一個更複雜的方法來了解導致高交互時間的各個因素。
圖:時間維度的框圖。橙色代表中間值
其中有些指標與時間相關,單位為毫秒。我們可以通過上述框圖,研究瀏覽器渲染頁面耗時的主要原因。導致長互動時間的各個因素的一種方法是做線性回歸,我們從其他指標預測域交互度。
也就是說,我們給每個指標分配一個權重,將一個頁面的域交互度時間建模為其他指標的加權和,再加上一些常數。優化算法設置權重,使整個數據集的預測誤差最小化。通過回歸發現的權重大小,可以告訴我們每個指標對頁面加載緩慢的影響的大小。
開發者從回歸算法中排除時間指標。如果我們花了 500 ms 建立連接,就會給域交互度增加 500ms,但這並不是我們想要的。時間指標從根本上說是結果。我們希望了解是什麼原因導致了它們。
圖:指標的回歸係數,預測域交互性
括號中的數字是通過優化算法得到的回歸係數,以毫秒為單位。雖然確切的數字應該有所保留(參見下面的注釋) ,但是看到每個特性分配的比例也很有趣的。例如,該模型預測,對於交付主文檔所需的每個重定向,速度會減慢 354 毫秒。每當主 HTML 文檔通過 HTTP2 或更高的方式傳遞時,該模型預測交互時間將降低 477 毫秒。該文檔每觸發一次請求,預計會多出 16 毫秒。
我們通過一個簡化的現實模型得到回歸係數。交互時間實際上並不是由這些輸入指標的加權和決定的。這個模型還無法發現有明顯的因果關係的因素。混雜多個變量讓分析變得更加困難。例如,如果通過 HTTP 2 加載主文檔與通過 HTTP 2 加載其他請求相關,那麼模型將把這個優勢加入到 main_doc_is_http2_or_greater 或者更大的權重中,來加速來自主文檔以外的請求。我們需要謹慎地將模型的結論與現實聯繫起來。
HTTP 協議版本對域交互有什麼影響?
下面是一個有趣的場景,由用於交付根 HTML 頁面的 HTTP 協議版本進行分割。
第一個請求的 HTTP 協議版本分割的域交互式框圖。橙色的線是中間值,方框的位置在第 25到 75 百分位之間。括號中的百分比是使用此協議發出的請求的分數。
還有一小部分網站使用 HTTP 0.9 和 HTTP1.0。而且這些網站速度也很快。我們看到,即使協議已經變得更快了,但程式設計師卻給瀏覽器提供更多的內容低效了這種提速。
這是用於交付根 HTML 頁面的協議版本。如果我們看一下協議對文檔中連結的資源的影響會怎樣?如果我們按照協議版本對請求數進行回歸,就會得到以下結果。
圖:通過協議版本對請求數進行分析,預測域交互度
根據上圖我們可以得出這樣的結論: 將請求的資源從 HTTP 1.1 遷移到 HTTP2 訪問速度可以提高 1.8 倍,而從 HTTP 2遷移到 HHTP 3 速度將降為原來的 0.6倍。那麼,HTTP3 真的是一個較慢的協議嗎?答案是否定的: 一個更可能的解釋是 HTTP 3 很少見,通過 HTTP 3發送的少數資源 (例如 Google Analytics) 對域交互的影響大於平均水平。
內容類型對域交互的影響是什麼?
讓我們根據傳輸的字節數、傳輸的數據類型來預測交互時間。
圖:由請求發起者傳輸的千字節的回歸係數,預測域交互度
下面分析不同的請求類型的請求數。
圖:請求發起者的請求數的回歸係數,預測域交互度
通過上圖可以看出,並非所有的請求都是平等的。由 link 元素(即 CSS、 favicon)觸發的請求,以及由 CSS (即字體、CSS)、腳本和 iframe 觸發的請求,都會大大降低請求的速度。通過 XHR 和 fetch 執行請求預示著比基準的域交互時間更快(可能是這些請求幾乎總是異步的原因)。CSS 和腳本通常以渲染阻塞的方式加載,因此發現它們與較慢的交互時間相關並不令人驚訝。視頻的請求也挺快。
經驗教訓
通過上述分析我們並沒有發現新的優化技巧,但是分析幫助我們預計各種優化影響。得到以下經驗:
儘量減少請求。請求的數量比請求傳輸的字節數對性能的影響更大。對於必須發起的請求,如果可能的話,請使用 HTTP2 或者更高版本的協議。儘可能避免使用阻塞式請求,儘可能使用異步加載。
庫
為了弄清楚頁面上使用了哪些庫,我們採取了以下方法: 在每個站點上我們重點關注全局變量 (即窗口對象的屬性)。然後,每個出現次數超過 6000 次的全局變量都與一個 JavaScript 庫相關聯(如果可能的話)。這是一項艱苦的工作,但由於數據集也有每個頁面的請求 URL,因此可以查看可變事件和 URL 請求之間的關係,這足以幫助我們確定哪個庫將設置全局變量。無法準確認定和某個庫油管的全局變量被忽略。這種方法在某種程度上不夠精確。JS 庫沒有義務在全局名稱空間中留下任何東西。當不同的庫設置相同的屬性時,會產生一些噪點,這種情況在作標記時會被忽略。
現在最常用的 JavaScript 庫是什麼?根據網上的各種會議和博客文章,你可能認為是 React,Vue 和 Angular 這三個。然而根據我們的排名,並非如此。
使用率最高的 10 個類庫
圖:查看類庫使用情況的完整列表
正如你所見,古老的 jQuery 使用率最高。它在 2006 年首發,已經有 14年的歷史。它已經經歷了幾百個版本的迭代。2006年進入 Web 2.0 的時代,這一年最常用的瀏覽器是 Internet Explorer 6,最大的社交網絡是 MySpace,網頁也開始支持圓角。JQuery 的主要優勢在於它跨瀏覽器的兼容性更好。然而,14 年後的今天,我們的樣本中依然有一半的網頁加載了 jQuery。
有趣的是,2.2% 的網站因為 JQuery 沒有加載而出錯。
根據前10 名來看,我們的瀏覽器主要運行分析、廣告和代碼,以便與舊的瀏覽器兼容。不知何故,8% 的網站定義了一個 setImmediate/clearImmediate polyfill,實現一個無法被任何瀏覽器實現的特性。
預測類庫的時間交互
我們將對資料庫的數據進行一次線性回歸,研究庫與域互動度的關係。回歸的輸入是一個向量 x,x. length = = 庫數,其中如果存在庫 i,x [ i ] = = 1.0; 如果不存在庫 i,x [ i ] = = 0.0。當然,我們知道域交互實際上並不是由某些庫的存在或不存在決定的。然而,將每個庫和加載速度之間進行建模,通過成千上萬的例子進行驗證,能夠給我們帶來一些有趣的發現。
通過回歸係數得到交互式時間的最佳和最差庫
圖:通過回歸係數預測域交互度
這裡的負係數表示存在該庫時模型預測的交互時間低於庫存不存在時。當然,這並不意味著增加這些庫會使你的網站更快,這只是意味著這些庫的網站碰巧比模型建立的基準更快。這裡的結果可能既有技術性,也是社會性因素。例如,用於延遲加載的庫預示著交互時間較短。這可能是因為使用這些庫的的程式設計師通過懶加載對性能進行優化的結果。
通過回歸係數得到 onload 時的最佳庫和最差庫
我們可以重複上面的實驗,但是這次預測的是記載時間。記載時間為窗口的 「load」 事件啟動所需的時間,也是頁面上所有資源加載所需的時間。我們用和以前一樣的方式做一個線性回歸。
圖:通過回歸係數得到 onload 時的最佳和最差庫
通過回歸係數得到 jsheapusedsize(JS對象佔用的內存) 的最佳庫和最差庫
這裡的預測是 JavaScript 使用的堆大小,以兆字節為單位。
網上很多人可能會認為:存在相關性並不等於存在因果關係。事實上,我們確實不能直接用這些模型得到因果關係。特別在問題涉及多種因素時,在解釋這些係數時我們應該非常慎重。該模型將交互時間較慢的 982 毫秒與 jQuery 的存在聯繫起來,而且有一半的站點加載了這個腳本,這應該引起我們的關注。如果你正在優化你自己的站點,交叉引用它的依賴列表與排名和係數這裡應該給你一個比較不錯的參考,可以看到哪些依賴刪除可以幫助你最大限度地降低資源消耗。
如果你對我們所爬的網站中發現的錯誤感興趣,請參閱我們 JavaScript 錯誤分析這篇文章 。在這篇文章裡,我們分析了網站中發現的錯誤,並討論了這些錯誤對我們的啟發,指導我們如何設計才更不容易出錯。