天天看點

前端優化系列 - 初始化的性能影響分析前言 常見的初始化 外殼初始化 核心初始化 業務初始化 ServiceWorker初始化 網絡初始化 伺服器初始化 JS初始化 結束語

資料表明,即使在資源有緩存的情況下,頁面首次通路的耗時也是非首次通路的兩倍。

為什麼首次通路這麼耗時呢,時間去哪裡了?本文詳細分析頁面首次通路耗時的原因。

我們先看看打開一個頁面,需要經過那些流程。可能會包括,外殼初始化,核心初始化,建立WebView,建立Renderrer程序,初始化V8 JS引擎,初始化IPC,初始化CC,初始化網絡庫,初始化檔案系統,初始化資料庫,啟動ServiceWorker線程,DNS解析,建立網絡連接配接,頁面伺服器初始化,等等。這些流程前端一般是看不見的。

在讨論具體的耗時之前,我們先約定,下文所有的資料都是基于Nexus 5手機。不同的手機的性能資料差異極大,一些高端手機(比如,iPhone X),性能可能是中低端機的好幾倍。

我們先看看浏覽器外殼的初始化,使用者點選桌面圖示啟動浏覽器,浏覽器會進入一個狀态機,按步驟初始化各個子產品,很多子產品的初始化會涉及網絡,檔案IO,JNI,等等操作,這些都會有一定的耗時。

當然,全新安裝首次啟動,外殼初始化的過程中,一般最耗時的是加載SO和JAR,其中使用DexClassLoader去加載JAR檔案,在一些中低端機器,特别是Android 5.0以前的系統,耗時是以秒計算的,有些甚至可以達到10秒。非全新安裝首次啟動,加載SO和JAR的耗時會大幅下降,大概在500ms。

我們為什麼需要關心浏覽器啟動的耗時呢?一些場景下,使用者通過掃碼或者點選桌面圖示去通路頁面,這個過程就會包含浏覽器的啟動流程,我們有必要了解這其中發生了什麼。

對于内置浏覽器核心的App,比如,支付寶,手淘,情況又是怎樣的呢?我們這邊暫時沒有支付寶和手淘的啟動性能資料,但子產品初始化,加載SO和JAR,這些流程都會有,時間不會很小。

在外殼初始化耗時方面,有沒有一些比較好的解決辦法呢?

最好的辦法就是程序保活,現在國内很多手機廠商都會給微信,支付寶,等超級App去程序保活,使用者在任務清單殺掉了應用,其實程序還在。

如果是多程序的情況,可以提前建立程序,比如,微信和支付寶的小程式,使用者通路時可以直接使用預建立的程序。

我們再來看看核心的初始化,與外殼的初始化類似,核心的初始化也需要加載SO和JAR,建立WebView和初始化各個功能子產品。

在建立WebView方面,全新安裝首次建立約1秒,非全新安裝首次建立約300ms,第二次建立約15ms。

首次建立Renderrer程序,初始化IPC,初始化CC,這些耗時在百毫秒的級别;

V8 引擎相關的初始化耗時也在百毫秒的級别,其中首次NewContext要20ms。

總的來說,首次通路加載SO和JAR一般需要500ms,建立WebView和走完核心流程一般需要消耗500ms,也就是說,提前初始化核心和預建立WebView加載一個URL,跑一趟核心流程,可以帶來約1秒的收益。

在頁面加載的過程中,核心會有很多回調通知外殼,這些回調的處理上是否可能存在性能問題呢?

我們發現,在一些App上,一些接口很可能會出現性能問題,比如,onPageStarted,shouldOverrideUrlLoading,shouldInterceptRequest。

這些接口為什麼會出現性能問題呢?一般很多應用會在首次onPageStarted回調時執行複雜的業務邏輯,比如,初始化一些統計子產品,進行JS注入,等等。需要說明的是,onPageStarted并不是同步接口,為什麼也會有影響呢?因為它是在UI線程執行的,長期占用UI線程,會對核心有較大的影響,核心很多操作需要抛轉到UI線程去處理,比如,ServiceWorker線程啟動就有抛轉UI的過程,在UI執行完之前,它隻能等待。

shouldOverrideUrlLoading 是用戶端攔截請求的關鍵接口,核心會同步等待,很多應用會有比較複雜的攔截規則。

shouldInterceptRequest 是用戶端離線包的關鍵接口,核心會同步等待,很多應用會在這個接口首次回調時去解壓離線包和初始化離線子產品。

在一些實際應用中,優化這些回調的處理,可以給全部H5頁面帶來 10% 以上的性能提升。

ServiceWorker是PWA的關鍵技術,它具有非常強大的能力,Fetch,Cache,Push和Add to home screen,能讓前端開發者非常靈活的操控頁面緩存。

同時,它也是有比較大的初始化成本的,比如,ServiceWorker線程啟動平均要200ms,而每次通路頁面,一般ServiceWorker線程至少都需要啟動一次。當然,Chrome也在不斷優化這塊的耗時,最終預計能優化到100ms以内。

在網絡初始化方面,一般核心網絡庫的初始化并不太耗時,耗時的是DNS和Connection。

使用者首次通路,一般都需要去進行DNS解析和建立連接配接,而在後續通路時,一般都可以用上緩存或者預連接配接。

DNS解析,一般耗時在200ms以上,建立HTTP連接配接,一般耗時也在200ms以上,而建立HTTPS連接配接則需要600ms以上。

也就是說,使用者首次通路時,如果不能提前建立連接配接,從性能的角度來說,是非常危險的。

這個方面我們的建議是,使用HTTPDNS提前解析和緩存DNS,提前建立連接配接(比如,使用者點選時)。

浏覽器也有這方面的優化,比如,在加載主文檔時,提前發起子資源的預連接配接,但在一些托管網絡庫的應用來說,這些政策可能不會生效。

頁面伺服器和資源伺服器,是否也需要初始化呢?一般也是需要的,比如,頁面通路過之後,頁面伺服器也會有一些緩存,使用者再次通路時可以直接使用緩存而無需走完整的流程,但這些緩存應該是大部分使用者都能共享的,是以實際影響不好評估。資源伺服器也一樣,比如,圖床,很多是按使用者手機螢幕和網絡類型來傳回不同圖檔的,使用者通路過就會放到CDN緩存中。

暫時未有資料表明伺服器初始化對頁面整體性能産生明顯影響。但我們有另外一份資料,在一個業務中,預建立WebView提前加載一次模版頁面,能讓全網平均性能優化100ms。其中,模版頁是304的,裡面的資源都是可緩存的,也就是說,這100ms的收益并不來于緩存,而是來于某些子產品的初始化。

這裡提到的JS初始化,并不是前面說的JS引擎相關的初始化。JS初始化是指JS檔案緩存到httpcache和解析編譯生成V8 Cache檔案。很多資料表明,JS解析編譯占JS耗時的35%以上,一些有巨型JS的頁面甚至可以達到80%。在U4 2.0中,一般JS執行一次之後,就可以生成V8 Cache,雖然V8 Cache可以重複使用,但也存在被自動清理的情況,是以提前執行一次還是有收益的。

一些業務中,提前執行一次JS,在使用者真實通路時,耗時從500ms降到200ms。特别是在一些超級App中,基礎JS基本都一樣,提前執行一次可能會帶來非常明顯的收益。

上面介紹了一些常見的初始化對頁面性能的影響,希望大家能了解到一些隐藏的資訊,能開闊Web優化的思路。當然,這些點不一定會存在很大的性能問題,比如,一些業務子產品處理的非常好的App,在業務初始化方面不一定會有性能問題,需要根據自己的實際場景,具體問題具體分析。

繼續閱讀