前端优化系列 - H5存储及优化

简介: 数据存储在性能优化中扮演着极其重要的角色,H5相关的存储非常多,本文详细介绍各种存储的特点和相关优化实践。

前言

数据存储在性能优化中扮演着极其重要的角色。存储类型,存取策略,存储命中率,等等,往往都会影响页面的实际性能。CPU Cache有多级缓存机制,浏览器有MemoryCache机制,服务器有CDN缓存,这些都为了更大程度的提升性能。本文详细介绍与H5页面相关的存储,以及可能的优化思路。

常见H5存储

H5页面相关的存储非常多,我们先看看有那些类型的存储。

e7e66dd6d8231b7c525d9d5feebc277d17de4001

通常,与页面加载性能相关的,有下面几种缓存,

(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,这是非常离谱的。但事实就是这样,资源从磁盘缓存响应回来的过程,会有各种线程抛转的额外成本。

82612e7fef316f8f3a22efe8c320da88840b5ab9

资源响应的成本,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。

655017fac34d8a756ecccb461123bc03d6fea6e3

用户在访问页面时,内核会优先从CustomMemoryCache里面查找,如果找到就直接返回资源响应。CustomMemoryCache里面存储的就是预置进来的资源。

ccb130d2aa2cc27e69cb287189f12583c53a1a6c

我们并不知道用户即将会访问什么页面,如果把大量的资源都预置进内存,而用户却没有使用,那就会造成浪费。另外,资源在内核内存,仅仅是加快了资源的加载速度,页面的首屏包含非常多非常复杂的流程,某个流程的加速并不一定能带来整体性能的提升,比如,非关键的JS放在内存,可能就会先于一些关键JS被提前执行,反而让首屏更慢。所以,选择放那些资源进内存也是非常有讲究的,能预置的资源一般是 非常关键的更新频率较低的少量公共基础资源,比如,手淘里面的web based基础JS,手淘小程序的基础JS,等等。

(2)预加载资源进HttpCache

预置资源进内存,对加载性能的提升是最明显的,但成本也是最大的,会占用用户手机宝贵的内存资源。另外一种预置资源的思路是,提前通过内核去预加载一些资源,资源加载回来之后就直接保存在标准的HttpCache。资源在HttpCache和在客户端缓存(比如,手淘ZCache)的性能差别不大。但如果资源不能放进ZCache,通过这种方式提前放到HttpCache,也是一种优化思路。

(3)使用WebViewCache极速切换页面

H5页面的加载流程是非常重的一套流程,即使同一个页面多次重复访问,也需要走比较完整的流程,耗时极长,这与用户的期望是不符的,通常用户期望访问过的页面就能快速展现出来。在一些特定的场景,H5也是可以做到极速展现的,比如,前进后退。其它的场景,比如页内几个TAB切换,是否也可以用上这类缓存呢?也是可以的,我们在天猫超市首页上就使用此缓存实现了类Native体验的底部导航

原理上也是比较简单的,在页面首次访问时,会将排版渲染好的页面放进WebViewCache里,WebViewCache是存储完整页面的一块内存。

4caf8920250c978156b271efc9bd5cf4c5fc76cf

用户再次访问该页面时,会将WebViewCache内存中的完整页面读取出来,直接绘制展现,而无需再进行加载解析排版渲染等流程,从而达到极速打开的效果。

453434eaaae8fc87a9cf19cefda320399fadc009

除了内核提供WebViewCache基础技术之外,前端也需要与内核进行一定的交互,比如,通过JSAPI查询当前页面是否在WebViewCache,如果在则返回它在WebViewCache列表的位置,然后前端就可以使用JSAPI去跳转到相应位置的页面,内核就把页面从内存读取和展现出来。使用此类技术,页面一般能在500ms左右完全展现出来,具有非常好的用户体验。

(4)前端使用LocalStorage缓存HTML文档

当前前端渲染非常流行,页面大部分的逻辑都会由前端JS去执行,JS执行完才会生成完整的HTML文档,而JS执行的成本是非常大的,JS执行时间可能占据首屏时间的50%,有些甚至能达到80%。那么,我们有没有可能将JS执行生成的完整HTML文档缓存起来呢,下次访问时直接使用已缓存的页面,而无需重复执行JS?这也是可以的,我们在天猫超市和天猫国际一些页面上已经使用此技术。

原理上也不复杂,首次访问页面时,JS执行完之后会生成完整的HTML文档,我们将HTML文档缓存到LocalStorage里面。

d79550e840e121bcbf44dfd0407fcea9ad1f8057

在后续的访问中,我们优先从LocalStorage里面读取HTML文档,解析排版渲染页面,而无需JS执行去生成页面,让页面展现速度得到极大的提升。

9c9b470dfa39f7aa53114636ea6e4d5ca4be0b12

这种方案的关键在于前端能够实现一套DOM-Diff更新的机制,在从LocalStorage读取HTML页面的同时,前端还会发起请求去更新HTML文档,在新的HTML文档回来之后,会和旧的文档进行Diff,针对Diff来进行局部更新,这样能保证页面得到及时的更新。

结束语

前面介绍的H5缓存相关理论与实践,期望能让大家对H5缓存有个整体的了解,理解一些优化的思路和背后的原因。一些优化展现了H5的极致能力,比如,从预置内存里面加载资源,可以在1ms完成一个资源的加载,从WebViewCache里面读取页面,在普通手机500ms就可以展现一个完整页面,这些速度简直是不可思议的,却真实的存在着!很多时候不是H5能力不行,而是H5没有做定制,如果H5进行定制约束优化,也一样可以带来极致的性能提升。

目录
相关文章
|
1月前
|
前端开发 JavaScript UED
优化前端性能的关键技巧
在当今数字化时代,网站和应用程序的性能对用户体验至关重要。本文将介绍一些优化前端性能的关键技巧,包括减少HTTP请求、使用CDN加速、精简代码等方面的实用建议,帮助开发者提升前端性能,提供更流畅的用户体验。
|
1月前
|
缓存 前端开发 JavaScript
揭秘前端性能优化:从代码到用户体验的全面升级
揭秘前端性能优化:从代码到用户体验的全面升级
19 0
|
1月前
|
Web App开发 缓存 前端开发
前端性能优化方案有哪些?
前端性能优化方案有哪些?
69 1
|
1月前
|
前端开发 JavaScript UED
使用React Hooks优化前端应用性能
本文将深入探讨如何使用React Hooks来优化前端应用的性能,重点介绍Hooks在状态管理、副作用处理和组件逻辑复用方面的应用。通过本文的指导,读者将了解到如何利用React Hooks提升前端应用的响应速度和用户体验。
|
2月前
|
前端开发 JavaScript 安全
从前端性能优化角度谈JavaScript代码压缩与混淆
本文从前端性能优化的角度出发,探讨了JavaScript代码压缩与混淆的重要性及实现方式,通过分析不同压缩混淆工具的特点和效果,为开发者提供了实用的指导和建议。
|
2月前
|
Web App开发 缓存 前端开发
前端性能优化:从入门到精通
【2月更文挑战第3天】
47 1
|
2月前
|
缓存 前端开发 JavaScript
如何优化前端性能提升用户体验
在Web应用中,前端性能是影响用户体验和转化率的关键因素之一。本文将介绍一些优化前端性能的方法,包括减少HTTP请求、使用缓存、压缩代码等。
|
2天前
|
存储 缓存 前端开发
前端如何利用indexDB进行数据优化
使用IndexedDB作为浏览器内置的客户端数据库,用于存储大量数据和实现离线支持。它能缓存常用数据,减少服务器请求,提高用户体验。IndexedDB支持数据索引、复杂查询及版本管理,允许离线操作并同步到服务器。但需熟悉其异步API,可借助Dexie.js、localForage等库简化使用。
|
5天前
|
机器学习/深度学习 前端开发 算法
使用机器学习优化前端用户体验
在当今高度竞争的互联网市场中,用户体验是至关重要的。本文将探讨如何利用机器学习技术来优化前端用户体验,从而提高用户满意度和留存率。我们将介绍如何利用机器学习算法分析用户行为数据,优化网站性能和内容推荐,以及如何实时调整界面设计和交互方式,从而实现个性化、智能化的用户体验。
|
6天前
|
前端开发 算法 JavaScript
如何优化前端性能:探索图片压缩与延迟加载技术
本文深入探讨了前端性能优化中的关键问题:图片压缩与延迟加载技术。通过介绍图片压缩的原理和方法,并结合实例说明了如何有效减少图片大小、提升加载速度;同时,详细解析了延迟加载技术的实现原理及其在提高页面加载性能中的作用,为前端开发者提供了实用的优化方案。