页面性能检测及其优化

avatar
@https://www.tuya.com/

作者: 涂鸦-水镜

来涂鸦工作: job.tuya.com/


性能优化的方向

前端性能的标准主要分为加载时性能和运行时性能,运行时性能表现指的是当你的页面在浏览器运行时的表现,本文就来分享下 React ProfilerPerformance 这两款用来检测运行时性能的工具

1、React Profiler

介绍

Profiler 用来测量渲染一个 React 应用多久渲染一次以及渲染一次的“代价”。 它的目的是识别出应用中渲染较慢的部分,我们可以直接在React应用中使用它,具体使用可以参考Profiler API,但我们这里主要讲解在浏览器开发者工具下的使用,这样可以更直观更清晰观察组件的渲染性能

react-dom 16.5+ 在 DEV 模式下才支持 Profiling,同时生产环境下也可以通过一个 profiling bundle react-dom/profiling 来支持。请在 react-profiling, 上查看如何使用这个 bundle

使用

DevTools 对支持新的 profiling API 的 APP 新加一个 Profiler tab 列。当你开始记录之后,DevTools 将会自动收集你 APP 在(启动之后)每一刻的性能数据。(在记录期间)你可以和平常一样使用你的 APP,当你完成 profile 之后,请点 “Stop” 按钮。

759cc92c-5f7b-47f0-b6dc-ae8f9dccdb18-image.png

每个Tab项的作用

323066a0-c23c-4760-add4-4b381effd3b0-image.png

在记录前首先启用一些比较重要的设置(打开设置面板请点击右上角齿轮⚙)

设置

开启 Highlight updates when components render

高亮渲染的组件

勾选这个设置以后,页面上所有render的组件都会高亮的线框包裹展示,效果图如下

注:该选项在生产环境也可以开启用来查看哪些组件正在渲染

4a3ba979-5153-4b6f-b894-dc17383bc94e-image.png

开启 Record why each component rendered while profiling.

816b8a70-2734-4990-a72d-2e56875f7b8b-image.png

开启后我们就可以在 commit 信息中查看到造成组件渲染的具体原因

查看性能数据

Commit 图

从概念上讲,React 的运行分为两个阶段:

  • render 阶段会确定例如 DOM 之类的数据需要做那些变化。在这个阶段,React 将会执行(各个组件的)render 方法,之后会计算出和调用 render 方法之前有哪些变化。
  • commit 阶段是 React 提交任何更改所在的阶段(在 React DOM 下,就是指 React 添加、修改和移除 DOM 节点的时候)。同时在这个阶段,React 会执行像 componentDidMount 和 componentDidUpdate 这类周期函数。

profiler DevTools 是在 commit 阶段收集性能数据的。各次 commit 会被展示在界面顶部的条形图中

以官网首页为例,在开启了profile以后,去hover其中的一个菜单

9f34b783-9e13-42e4-82b3-f16bbeaad56d-image.png

可以看到本次操作一共触发了9次commit

be644d3c-39b5-443e-ab32-6391c2a52bdc-image.png 条形图中的每一列都对应这一次commit操作,每一列的高度代表这次commit操作的时间(较高、偏黄的列会比较矮、偏蓝的列花费的时间多),点击其中的一列即可展示这次commit的具体信息

火焰图

火焰图就像是一颗虚拟dom树,他会展示你所指定的那一次 commit 的信息。图中每一列都代表了一个 React component,各列的尺寸和颜色表示这列所代表的 component 及其 children 的渲染时间(列的宽度表示该 component 最近一次渲染所花费的时间,列的颜色代表在该次 commit 中渲染所花费的时间)。黄色代表耗时较长、蓝色代表耗时较短,灰色代表该 component 在这次 commit 中没有被重新渲染。

71247a20-ea3c-4c22-911a-ff213b363370-image.png

例如上图,可以发现在这次 commit 中 Header组件发生了重渲染,通过点击 Header 组件来查看详情

4249f477-6f02-4d5b-8b62-1d4ae455a460-image.png

可以看到本次渲染共花费1.3s,其中渲染时间最长的组件是Header组件,他的渲染时间为18.2ms,并且造成其渲染的原因为Hooks Changed,Header组件中最耗费性能的子组件为HeaderDrawer,其渲染时间为16.9ms,再点击HeaderDrawer组件查看详情

5412fff8-8c33-4bd2-b8f7-61c9c174e268-image.png

可以看到造成HeaderDrawer渲染的原因有两个,并且其渲染时间长主要是因为HeaderDrawer有众多子组件进行了首次渲染

排序图

同火焰图一样,排序图也会展示你所指定的那一次 commit 的信息,图中每一列都代表了一个 React component。不同的是排序图不再是以dom树的结构展示,而是有对组件通过耗时进行了排序,耗时最长的 component 会展示在第一行。这样我们就可以在大量渲染中找到渲染时间最长的组件

06b9b901-b05f-4dc6-9a39-ecd6a7732f7a-image.png

使用总结

1、首先通过高亮render组件来发现那些地方存在render 2、通过查看火焰图来找到渲染时间长的组件 3、通过查看渲染的组件来找到触发渲染的原因,查看触发渲染的原因是否正常 4、如果渲染的组件过多可以通过排序图来找到渲染时间最长的组件

2、Chrome Devtool Performance

介绍

React Profiler 可以帮助我们快速定位react应用的性能瓶颈,但是如果我们的应用没有使用react的话应该怎样分析从而找到性能瓶颈呢,这时我们可以使用另一款开发者工具 Performance

83ab5c7f-8bc8-4270-a21c-94bc88434a1e-image.png

在使用前可以先根据自己需要来进行设置,简单介绍下每一个设置项的作用

1、Screenshots 截图:默认勾选,每一帧都会截图 2、Memory 内存消耗记录:勾选后可以看到各种内存消耗曲线 3、Disable javaScript samples 关闭javaScript样本:减少在手机运行时的开销,模拟手机运行时勾选 4、Network 网络模拟:可以模拟弱网条件下运行页面 5、Enable advanced paint instrumentation(slow) 记录渲染事件的细节 6、CPU:模拟低性能CPU下运行性能

使用

当你开始记录之后,DevTools 将会自动收集你 APP 在(启动之后)每一刻的性能数据。(在记录期间)你可以和平常一样使用你的 APP,当你完成 profile 之后,请点 “Stop” 按钮。

a44a2a1c-d918-4bfc-8739-6a018eca21dd-image.png

结束记录后我们会得到上面的这样一张图,一眼看过去信息很多,但是我们主要需要关注的信息主要有这几部分

FPS

绿色越高越好,出现红色则表示FPS低,可以在Frames中看到具体的FPS值

59e485cb-d324-460f-ba46-0594275b916a-image.png

如果出现一条红色的线,则表明FPS过低

5904bc61-e54e-4abc-9209-84755c391d76-image.png

CPU

下图表示处理各个任务花费的时间,选择一段CPU统计可以在区域四的Summary看到统计表格,在Main区域我们可以查看调用栈的执行,例如下图首先执行requestAnimationFrame这个API,然后用调用了app.update这个函数,然后造成了Recalculate Style、Layout、Update Layer Tree、Paint这系列浏览器计算样式,然后布局、更新渲染树最后绘制到屏幕上的操作

0f4bbd13-5613-4e25-a97f-949a1d67099a-image.png

在事件长条的右上角处,如果出现了红色小三角,说明这个事件是存在问题的,需要特别注意。

6a8a39b7-046e-465c-b4a5-6b79bee97e81-image.png

举个例子

谷歌提供了一个用来分析的DEMO。这个页面里都是很多上下移动的蓝色小方块。点击Add 直到页面发生卡顿,再点击Optimize发现卡顿消失,我们对卡顿时的页面进行一波性能分析

ed293088-3fff-4726-99cf-bb85d4dfc4d7-image.png

可以发现一片红,简单观察发现其中FPS上出现了一条红线,说明FPS过低,在看Main中的小方块都出现了小红三角,说明每次的事件调用都是存在问题的,并且发现Summay图中Rendering占用时间过长

73d2cb80-0ed5-40ec-85d1-dd32cc4ff884-image.png

通过选中app.update这个方法下导致的一次布局可以发现,有一个warning提示存在强制回流的操作,并且定位到了app.js的第71行,如果是在开发环境下代码没有被压缩,我们可以直接点击跳转到源码处查看

08e919f7-2310-4aa2-bc23-ab5e8012d4cc-image.png

通过查看源码并且结合warning给出的提示,我们发现在优化前的代码里存在频繁的dom位置的读取和更新操作,这也就是页面卡顿的问题所在了,我们知道浏览器回流重绘操作类似于React的setState,浏览器会将批量的重绘回流操作放到一个执行队列中,当执行队列达到一定数量或者到达指定条件时才会批量的执行这一批重绘回流操作,这样就避免了频繁的重绘和回流操作,但是在React中setState的表现是异步的,我们无法拿到最新的state,可是在浏览器中不管dom怎样运动,我们每次都能拿到他最新的位置信息,这时因为在浏览器中如果js执行了读取dom位置信息的操作,那么浏览器会立即清空执行队列来进行重绘回流,保证js能够读取到最新的位置信息。

具体细节可以查看:

1、developers.google.com/web/fundame…

2、muyiy.cn/question/br…

那该如何优化这种操作呢?我们可以看下面优化后的代码,发现优化后将直接读取offset改为了读取style.top,这样可以拿到最新的位置信息。并且虽然style上位置信息更新了,但是回流的操作仍在执行队列中,所以不会导致强制回流

点击优化后我们再对其进行一波性能分析,可以看到优化后的性能信息又恢复到了正常标准

64a1b015-ca8a-4f9a-b91a-5e6bf435524c-image.png

总结

本文简单介绍了两款性能检测的工具,在开发中我们可以时不时的用来检测一下,查看是否存在比较明显的性能问题然后及时优化掉。


来涂鸦工作: job.tuya.com/