当前位置: 首页 > 科技观察

实例分析:《奇趣百科》性能优化

时间:2023-03-15 22:09:16 科技观察

Trollbakery历经一年大改版。无论是内容还是程序架构,使用Vue.js修改后的MVVM概念都加快了开发进程。但是改版后出现了明显的性能问题,出现了明显的页面卡顿,所以我们又做了一次性能优化。本文主要介绍如何使用ChromeDevTools中的TimelineProfiles等工具。1.组件粒度加粗。首先想到的是用Timeline看:Frames还是正常的,但是我注意到内存占用差不多17MB,明显比改版前的12MB高了很多(由于空间问题上图),那么我们继续追踪内存相关,使用Chrome开发者工具的Profiles查看当前内存使用情况:打开Chrome开发者工具->点击Profiles控制面板->选择TakeHeapSnapshot->点击TakeChecked抓拍发现listItemHead、listItemImg、listItemMeta和listTuwen分别有9个或10个组件对象(9个是因为业务逻辑问题)。Vue.js支持组件系统。因此,为了提高复用性,我将卡片定义为一个组件,这个组件由若干个组件组成。卡片本身是一个listTuwen组件,它包括三个组件:listItemHead、listItemImg和listItemMeta。接下来我们研究一下快照表中对应的列代表什么。一个对象有两种形式持有内存:直接占有和间接引用,分别对应快照中的ShallowSize和RetainedSizeShallowSize。ShallowSize表示对象直接占用的内存大小。一个标准的JS对象通常会持有内存,用于描述自身的逻辑和存储立即数(属性值)。通常,只有字符串和数组类型可能具有更大的ShallowSize。RetainedSizeRetainedSize表示当前对象引用的其他对象占用的内存大小。当前对象销毁时,这部分内存会被释放。巨魔百科首页底部加载总共可以加载300张卡片30次。加载30次后,我们对比刚进入首页时的情况:内存使用率飙升至70MB,我们切换到Comparisonview(红框),选择Snapshot1(红框)。#New一栏显示三个组件的对象增加了290(289是因为业务逻辑),SizeDelta一栏显示三个组件的对象增加了内??存7M左右,加起来是20+MB。因此,我们可以得出结论,同一页面中大量复用的组件,尽量不要嵌套其他组件,否则内存占用会随着组件数量的增加而快速增加。可以看出,一个Vue组件对象内部引用了大量的其他对象,包括directives、watchers等,以及一些系列的getter和setter方法。解决方法是不使用卡片内部的组件,一张卡片只有自己的组件,使用其他方法来提高代码的复用性。最后对比一下优化后的结果:300张卡见底加载后,占用内存约40M。虽然listTuwen组件的对象占用的内存要大很多,但是整体减少了40%,优化效果还是很明显的。移除视口外不需要的DOM页面上DOM的数量越少,占用的内存越少,性能越好,这是一个很容易得出的结论。参考手机淘宝搜索结果页面,我们可以将窗口外不可见的卡片去掉,等滚动回窗口内(或滚动位置接近窗口某个像素值)再插入显示。List的卡片数量保持恒定值,而不是直线增长。从Trollbakery上的代码可以看出,我们的卡片数量保持在30张,也就是DOM的数据是不变的,不会随着页面向下滚动而改变。接下来我们看一下内存情况来验证我们的方法的效果:Timeline工具优化前后的内存曲线:内存曲线呈锯齿状,触发了垃圾回收,但是优化后曲线的斜率比优化前少了2°,即内存上升速度慢。另一个是优化前后的Profiles对比('DOM'作为关键词过滤):内存优化后,内存减少近10M,DOM的数据减少10倍,内存减少使用量减少了25%。在图片懒加载优化之前,先普及一下Timeline控制面板。最好在浏览器的隐身模式下使用,并禁用所有不相关的插件,因为插件也会占用内存,影响测试结果。如果需要记录网络请求,最好禁用浏览器缓存。从网上偷的一张图(来源):切换关注焦点有三种模式:Events:显示所有事件的记录Frames:显示页面渲染的帧数Memory:在这里显示页面的内存状态我们专注于Frames模式。页面上的每一帧内容都是由GPU绘制的,其最大绘制频率受限于显示器的刷新率。大多数情况下,最高的绘制速率只能是每秒60帧(framespersecond,即fps),对应显示器的60Hz。所以在页面性能的测试中,60fps是一个非常重要的指标,越接近越好。这是一个常数——屏幕刷新率为60Hz。60Hz与60fps有什么关系?不相关的。fps代表GPU渲染图像的频率,Hz代表显示器刷新屏幕的频率。对于一张静态图片,你可以说这张图片的fps是0帧每秒,但是你一定不能说此时屏幕的刷新率是0Hz,也就是说刷新率不随图片内容的变化。不管是游戏还是浏览器,当我们说掉帧的时候,就是GPU渲染图片的频率降低了。比如降到30fps甚至20fps,但是因为视觉暂留原理,我们看到的画面依然是动的、连贯的。帧模式下的帧是“帧”。“一帧”(Frames模式中的一列)表示为了在一帧(frame)中显示内容,显示器需要完成的工作,包括执行JavaScript、处理事件、更新DOM、改变样式和布局以及绘制页面.在Frame视图中,有两条横线贯穿视图,分别标示了60FPS和30FPS的基准。注意到有些栏目是空白或者灰色的,代表:空白:空闲时间灰色:还没有记录的活动,可以理解为c++在浏览器内部的一些工作,与前端js无关和渲染关系。现在看项目的Frames:超过60fps的columns相当多,而且大部分column的颜色都是绿色。先解释一下列颜色的含义:蓝色:网络和HTML解析黄色:JavaScript脚本运行紫色:样式重新计算和布局(Layout,RecaculateStyle,UpdateLayertree)绿色:绘图和合成(Paint,CompositeLayers)所以我们大部分时间都花在画画上。我们选择一些比较高的柱子,看看它们有什么特点:我们看到有一些空的绿色色块和实心的绿色色块,是这样的:分两部分画StepbyStep:DrawingandRenderingPainting:这包括一系列的你想画的东西,而这些都是从元素上的CSS派生出来的。渲染:逐个分析您在上一步中想要“绘制”的内容,利用GPU合成填充这些内容的实际像素。这部分我翻译的很烂,因为我自己也不明白具体的意思,大家可以看原文→关于绿条和Painting包括这些事件:事件描述CompositeLayers当Chrome的渲染引擎完成图像层合并时触发ImageDecode解码图像资源后触发图像调整大小。调整图片大小后触发画图。合并图层绘制到相应显示区域后触发。根据我的观察,我发现较高的绿色柱子一般包含多个ImageDecode事件,这些事件在图片加载回来的时候触发,进而产生大量的RasterizePaint事件,所以我猜想,分别加载图片,不要一次Load10张图片,所以要分散事件,将高大的柱子拆分成多个短柱子,这样可以进一步提高流畅度。图片延迟加载是如何实现的我就不多说了。类似的效果可以参考淘宝首页。最后来看一下优化后的Timeline:这个情况已经达到了理想状态,实际运行起来还是比较流畅的。但值得一提的是,PM最终并没有采用这一步优化,因为经过体验,PM认为图片懒加载会让用户感觉卡顿,并不是真正的滑动卡顿,而是整体体验卡。所以最终,从用户体验的角度来看,我们的优化方案并没有采用图片的懒加载。