摘要: 數據存儲在性能優化中扮演着極其重要的角色,H5相關的存儲很是多,本文詳細介紹各類存儲的特色和相關優化實踐。
數據存儲在性能優化中扮演着極其重要的角色。存儲類型,存取策略,存儲命中率,等等,每每都會影響頁面的實際性能。CPU Cache有多級緩存機制,瀏覽器有MemoryCache機制,服務器有CDN緩存,這些都爲了更大程度的提高性能。本文詳細介紹與H5頁面相關的存儲,以及可能的優化思路。前端
H5頁面相關的存儲很是多,咱們先看看有那些類型的存儲。web
一般,與頁面加載性能相關的,有下面幾種緩存,小程序
(1)MemoryCache瀏覽器
MemoryCache,資源存放在內存中,通常資源響應回來就會放進去,頁面關閉就會釋放。內存存取性能可達磁盤緩存性能的100倍,但這還不是MemoryCache的最大優點,MemoryCache最大的優點是離排版渲染引擎很是近,能夠直接被讀取,甚至無需通過線程轉換。在真實的頁面訪問過程當中,獲取資源的時間,磁盤IO僅僅是其中的一部分,更多的時間每每消耗在各類線程拋轉。緩存
(2)ClientCache性能優化
ClientCache,客戶端緩存,好比,手淘裏的ZCache(離線壓縮包緩存),本質上屬於磁盤緩存。這類Cache的優勢是能以相對可控的方式讓資源提早緩存在磁盤,但它也有一系列的成本。好比,它須要一套服務器與客戶端協同的下發更新邏輯,服務器端須要管理下發,客戶端須要提早解壓縮。咱們可能以爲提早解壓並非什麼弱點,但若是有一千個離線包,這個問題就比較嚴重了,若是不提早解壓,就沒法保證首次訪問性能,若是提早解壓會讓IO很是繁忙,可能會形成客戶端打開時嚴重卡頓。服務器
(3)HttpCache網絡
HttpCache,是歷史比較悠久的緩存,它利用標準的Cache-Control與服務器端進行協商,根據標準的規則去緩存或更新資源。它應用很是普遍,是很是有效果的一種磁盤緩存。它的缺點是徹底由瀏覽器按標準規則控制,其它端的控制力度很是弱。好比,某些被HttpCache緩存的靜態資源出問題了,一般只能是改頁面,再也不使用出問題的資源,而沒法主動清除出問題的資源。負載均衡
(4)NetCache前端優化
網絡相關的Cache,通常是指DNS解析結果的緩存,或預鏈接的緩存。DNS預解析和預鏈接是很是重要的,建立一個Https鏈接的成本很是大,一般須要600ms以上,也就是說,頁面若是有關鍵資源須要全新建鏈接,秒開基本是不可能了。
(5)CDN
CDN通常是經過負載均衡設備根據用戶IP地址,以及用戶請求的URL,選擇一臺離用戶比較近,緩存了用戶所需的資源,有較好的服務能力的服務器,讓用戶從該服務器去請求內容。它能讓各個用戶的緩存共享,縮短用戶獲取資源的路徑,來提高總體的性能。
固然,還有其它很是多類型的Cache,好比,
(1)JS相關,V8 Bytecode Cache,字節碼緩存,能極大的減小JS解析耗時,甚至能夠提高3-6倍的性能。參考:前端優化系列 - JS解析性能分析
(2)渲染相關,圖片解碼數據緩存,是一塊很是大的內存緩存,約100M,能保證頁面滾動過程能夠實時獲取到圖片解碼數據,讓滾動很是流暢。
(3)頁面相關,頁面緩存,Safari的PageCache,Firefox的Back-Forward Cache,UC瀏覽器的WebViewCache,都是同樣性質的緩存,將整個執行過的頁面保存在內存。標準的頁面緩存,進入的條件很是苛刻,大部分狀況都沒法進入,並且在前進後退的場景才容許使用。
前面提到,MemoryCache與其它磁盤緩存的最大差別並不在於磁盤IO成本,爲何這樣說呢,咱們先看看一個HTML文檔資源請求的完整過程,
-> Browser UI thread: load url
-> Renderer: set up document loader & do request
-> Renderer: If can use MemoryCache, return resource, commit navigation (直接使用MemoryCache的資源)
-> Browser IO thread: set up request
-> Browser UI thread: NavigationThrottle::WillStartRequest(回調onPageStarted)
-> Browser IO thread: start shouldInterceptRequest
-> Browser UI thread: shouldInterceptRequest, get cache from Zips(從客戶端離線包獲取資源)
-> Browser IO thread: use shouldInterceptRequest response
-> Browser IO thread: use HttpCache or start network request(使用HttpCache或從網絡請求資源)
-> Browser UI thread: NavigationThrottle::WillProcessResponse(進行MIME Sniff檢測)
-> Browser IO thread: send response to renderer(響應數據回傳到Blink內核)
-> Renderer: set response to MemoryCache, commit navigation(設置響應數據到MemoryCache,供後續流程使用)
咱們能夠很是清晰的看到,若是資源在MemoryCache,基本是沒有加載成本的。但若是在其它Cache,好比,離線包緩存,HttpCache緩存,除了磁盤IO時間,更多的是各類線程拋轉的時間,在UI線程特別繁忙的時候,從IO拋消息到UI,可能要200ms,這是很是離譜的。但事實就是這樣,資源從磁盤緩存響應回來的過程,會有各類線程拋轉的額外成本。
資源響應的成本,MemoryCache幾乎是零成本,能在1ms的級別給到內核引擎使用。ZCache和HttpCache的成本都較大,而HttpCache的成本相對少一些,它不須要再走到客戶端的外殼層。一般資源從ZCache響應回到內核Blink引擎層須要100ms,UI線程特別繁忙時,ZCache裏的主文檔資源響應甚至要300ms。
通常來講,較好的優化思路就是找到性能瓶頸,而後把瓶頸優化掉,瓶頸位置找的不對,可能就事倍功半。從咱們的一些經驗來看,加載流程中會影響性能的,通常有下面幾個環節:
(1)IO性能
內存存取性能能夠達到磁盤IO性能的100倍,可是,硬件性能突飛猛進,磁盤IO性能也是毫秒級別了,即便慢了100倍,實際也就慢了幾毫秒,即磁盤IO性能一般不會成爲性能瓶頸。固然,網絡IO則另當別論,可能存在較長的等待時間。
(2)網絡性能
若是資源請求走了網絡,一般時間是消耗在DNS解析和建立鏈接,固然,資源特別大時傳輸也有可能比較耗時,吞吐能力比較差的服務器響應時間也可能比較長。DNS解析可使用HttpDNS之類的技術去進行預解析。建立鏈接方面,能夠用Http2.0下降影響或者實現一些預鏈接策略。資源走網絡的成本是很是大的,咱們通常都指望資源能走本地緩存。
(3)線程拋轉
一般咱們認爲資源在本地磁盤了,獲取資源的耗時主要在磁盤IO,而事實上,磁盤IO的性能實際上是比較好的,更大量的時間消耗在資源響應傳遞迴內核Renderer線程的過程。若是資源在MemoryCache,那麼內核各個模塊(排版,渲染,JS引擎)是能夠直接使用的,幾乎是零成本。
那麼,什麼資源能夠放進MemoryCache呢?一般,頁面資源加載回來都會放進MemoryCache,以供內核各模塊高性能使用,MemoryCache在內核是使用GC管理的,頁面關閉時,頁面資源沒有關聯引用,就會從MemoryCache裏移除,便可以簡單理解爲在頁面訪問過程當中資源纔會保存在MemoryCache,頁面關閉時就會移除。
(4)整頁緩存
從性能的角度來看,整頁資源緩存的效果無疑是最好的。標準上,有PageCache,Back-Forward Cache,一些瀏覽器也有本身的實現方案(好比,UC的WebViewCache),這些技術都能將整個頁面的資源緩存在內存,在前進後退等特殊場景,能直接從內存裏讀取整個頁面,達到極速打開的效果。
前面介紹了不少理論層面的內容,咱們接下來介紹一些實踐優化案例。
(1)預置資源進MemoryCache
咱們先看看預置的過程,在頁面的onPageFinished的回調裏面去檢查是否有資源能夠預置,若是有,就經過相關接口把資源設置進內核的MemoryCache。
用戶在訪問頁面時,內核會優先從CustomMemoryCache裏面查找,若是找到就直接返回資源響應。CustomMemoryCache裏面存儲的就是預置進來的資源。
咱們並不知道用戶即將會訪問什麼頁面,若是把大量的資源都預置進內存,而用戶卻沒有使用,那就會形成浪費。另外,資源在內核內存,僅僅是加快了資源的加載速度,頁面的首屏包含很是多很是複雜的流程,某個流程的加速並不必定能帶來總體性能的提高,好比,非關鍵的JS放在內存,可能就會先於一些關鍵JS被提早執行,反而讓首屏更慢。因此,選擇放那些資源進內存也是很是有講究的,能預置的資源通常是 很是關鍵的更新頻率較低的少許公共基礎資源,好比,手淘裏面的web based基礎JS,手淘小程序的基礎JS,等等。
(2)預加載資源進HttpCache
預置資源進內存,對加載性能的提高是最明顯的,但成本也是最大的,會佔用用戶手機寶貴的內存資源。另一種預置資源的思路是,提早經過內核去預加載一些資源,資源加載回來以後就直接保存在標準的HttpCache。資源在HttpCache和在客戶端緩存(好比,手淘ZCache)的性能差異不大。但若是資源不能放進ZCache,經過這種方式提早放到HttpCache,也是一種優化思路。
(3)使用WebViewCache極速切換頁面
H5頁面的加載流程是很是重的一套流程,即便同一個頁面屢次重複訪問,也須要走比較完整的流程,耗時極長,這與用戶的指望是不符的,一般用戶指望訪問過的頁面就能快速展示出來。在一些特定的場景,H5也是能夠作到極速展示的,好比,前進後退。其它的場景,好比頁內幾個TAB切換,是否也能夠用上這類緩存呢?也是能夠的,咱們在天貓超市首頁上就使用此緩存實現了類Native體驗的底部導航。
原理上也是比較簡單的,在頁面首次訪問時,會將排版渲染好的頁面放進WebViewCache裏,WebViewCache是存儲完整頁面的一塊內存。
用戶再次訪問該頁面時,會將WebViewCache內存中的完整頁面讀取出來,直接繪製展示,而無需再進行加載解析排版渲染等流程,從而達到極速打開的效果。
除了內核提供WebViewCache基礎技術以外,前端也須要與內核進行必定的交互,好比,經過JSAPI查詢當前頁面是否在WebViewCache,若是在則返回它在WebViewCache列表的位置,而後前端就可使用JSAPI去跳轉到相應位置的頁面,內核就把頁面從內存讀取和展示出來。使用此類技術,頁面通常能在500ms左右徹底展示出來,具備很是好的用戶體驗。
(4)前端使用LocalStorage緩存HTML文檔
當前前端渲染很是流行,頁面大部分的邏輯都會由前端JS去執行,JS執行完纔會生成完整的HTML文檔,而JS執行的成本是很是大的,JS執行時間可能佔據首屏時間的50%,有些甚至能達到80%。那麼,咱們有沒有可能將JS執行生成的完整HTML文檔緩存起來呢,下次訪問時直接使用已緩存的頁面,而無需重複執行JS?這也是能夠的,咱們在天貓超市和天貓國際一些頁面上已經使用此技術。
原理上也不復雜,首次訪問頁面時,JS執行完以後會生成完整的HTML文檔,咱們將HTML文檔緩存到LocalStorage裏面。
在後續的訪問中,咱們優先從LocalStorage裏面讀取HTML文檔,解析排版渲染頁面,而無需JS執行去生成頁面,讓頁面展示速度獲得極大的提高。
這種方案的關鍵在於前端可以實現一套DOM-Diff更新的機制,在從LocalStorage讀取HTML頁面的同時,前端還會發起請求去更新HTML文檔,在新的HTML文檔回來以後,會和舊的文檔進行Diff,針對Diff來進行局部更新,這樣能保證頁面獲得及時的更新。
前面介紹的H5緩存相關理論與實踐,指望能讓你們對H5緩存有個總體的瞭解,理解一些優化的思路和背後的緣由。一些優化展示了H5的極致能力,好比,從預置內存裏面加載資源,能夠在1ms完成一個資源的加載,從WebViewCache裏面讀取頁面,在普通手機500ms就能夠展示一個完整頁面,這些速度簡直是難以想象的,卻真實的存在着!不少時候不是H5能力不行,而是H5沒有作定製,若是H5進行定製約束優化,也同樣能夠帶來極致的性能提高。
本文做者:atuanxy
本文爲雲棲社區原創內容,未經容許不得轉載。