这是 Jerry 2021 年的第 66 篇文章,也是汪子熙公众号总共第 343 篇原创文章。
国庆黄金周开始的前一天,9月30日,我所在的开发团队收到了一个关于 Angular 应用的服务器端渲染(Server Side Render,简称为 SSR)的客户 incident,让我有机会学习如何使用 Chrome 开发者工具来分析 Web 应用的性能问题。
Jerry 之前曾经写过一篇关于 Chrome 开发者工具的文章:Jerry和您聊聊Chrome开发者工具,本文算是该文的后续。
本来我是想用 Angular 应用为例来介绍工具的用法,但考虑到本公众号一万多读者里面,绝大多数前端开发朋友们,使用的还是 SAP UI5,因此最后还是选择了基于 SAP UI5 应用来介绍使用 Chrome Dev Tools 进行 Web 应用性能分析的使用步骤。
本文提及的 SAP UI5 应用,指通过 FreeStyle 方式开发的 SAP UI5 应用。通过 Fiori Elements 开发的 SAP UI5,不在本文讨论之列。
根据我以往的工作经验,如果一个 SAP UI5 应用出现了性能问题,很多时候性能瓶颈都出在该应用消费的后台 API,比如 OData 服务或者其他 AJAX 调用。
同 Angular 相比,SAP UI5 框架为应用开发人员屏蔽了 Web 前端开发中的很多底层细节,比如 DOM 操作,CSS 的编辑,数据双向绑定和事件注册等逻辑,均使用 UI5 封装之后的方式来完成。因此一个 SAP UI5 应用,纯粹的前端代码里,由于应用人员编码失误导致性能问题的概率相对 Angular 要低得多。
因此,为了演示 Chrome 开发者工具进行 SAP UI5 性能分析的使用方法,我在自己的 SAP UI5 脚手架应用里,故意编写了一些会引起性能问题的前端代码,然后通过 Chrome 开发者工具,把这些导致性能问题的代码进行定位。
我这个 SAP UI5 脚手架应用可以通过如下 url 访问:
https://wangzixi-diablo.github.io/ui5-toolset/webapp/index.html
这个 UI5 应用的视图我采取 JavaScript 视图类型实现,在视图实现文件 App.view.js 的 createContent 方法里,我调用了一个函数 heavyFunction,该函数在一个 for 循环里创建了一万个 div 标签,添加到 DOM 树中,然后插入一亿个元素到数组中。
我期望的结果是,能够使用 Chrome 开发者工具,对该 SAP UI5 应用进行性能分析,通过工具的帮助,将性能问题快速定位到这个 heavyFunction 函数。
其实使用 Chrome 开发者工具的 Performance 面板对 Web 应用进行性能分析,使用方式和 SAP ABAP 数据库性能分析工具 ST05 非常类似,即开启跟踪模式,在该模式下运行数据库事务(Web 应用),然后关闭跟踪模式,然后分析 ST05 (Chrome 开发者工具) 输出的跟踪结果。
首先打开一个 Chrome 隐身窗口,这是为了排除 Chrome 安装的其他扩展对性能分析可能造成的影响。
打开 Chrome 开发者工具,选择 Performance 标签页,打开设置选项,Network 选择成 Slow 3G,CPU 选择成六倍速降低:6x slowdown.
然后点击 Record 图标,开启性能跟踪模式:
刷新应用,使其在性能跟踪模式下运行:
等待页面显示完毕后,点击 Stop 按钮,关闭跟踪模式。
然后我们就能看到下图所示的跟踪结果:
大家请看下图代表 CPU 执行活动的这根条状图,其中在两个时间点之内,有大量密集的脚本执行(scripting)和渲染(rendering)的活动记录。
我们首先详细分析,为什么会有如此密集的 scripting 操作。移动下图所示的两根滑动条,缩小我们排查问题的时间戳范围(range).
时间戳范围越小,显示的明细越具体。大家注意到上图我用红色矩形框高亮的 Main 下拉框了吗?该下拉框之下,显示的就是在指定的左右时间戳范围内,Main 即主线程执行的 CPU 活动明细。
我们把 Main 下拉框显示的内容拖至底部,一下子就发现了我们故意编写的 heavyFunction 赫然在列,在我们指定的时间戳范围内,总共花费了 6.799 秒的时间去执行。
点击 App.view.js 的超链接,还能直接跳转到对应的代码去:
下面我们再调整左右时间戳滑动条,来研究 Rendering,即下图紫色条代表的活动。我们观察到,每一根紫色条,右上角都有一个红色三角形图标。鼠标放上去,tooltip 会显示:Forced reflow is a likely performance bottleneck.
意思是,强制回流是应用一个可能的性能瓶颈。
上图我们能观察到 Recalculate Style 和 Layout 紫色条,分别是 Rendering 的两种不同的操作类型。
我们在代码编辑器里编写的 HTML 代码,在从服务器端被加载到浏览器并被解析,到呈现在最终用户眼前,需经历上图所示的几个步骤:执行 HTML 页面里的 JavaScript 代码,构建 DOM 树,将文本格式的 CSS,转换成浏览器可以理解的数据结构,计算元素样式,生成渲染树,遍历渲染树,计算渲染树中每一个节点的大小和位置,创建待绘制列表,最终进行绘制(Paint)和合成(Composite).
而 Web 应用在使用过程中,由于用户与页面交互,导致 JavaScript 代码执行,可能会使得页面元素的大小及位置等属性发生改变,从而触发浏览器重新计算布局,最终重新渲染页面。我们称浏览器这种行为叫做回流(reflow),即我们在上图 Chrome 开发者工具里观测到的警告信息:
Forced reflow is a likely performance bottleneck.
回到我们的例子,受到回流操作影响的元素总数为 10087,触发回流操作的代码:ResizeHandler-dbg.js.
为什么文件 ResizeHandler-dbg.js 的第 170 行会触发浏览器回流呢?我们直接单击上图的超链接,可以直接定位到第 170 行代码。
首先,在 Bar-dbg.js 的 onAfterRendering 钩子函数(绿色)里,会调用 _handleResize(黄色), 检测当前是否应该抛出 "resize" 事件。这个检测最终被投递交给 ResizeHandler.checkSizes(橙色)进行处理。
Resize 检测逻辑也很简单,比较 DOM 元素新旧 width 和 height 是否相同。若不相同,则如下图红色矩形框内代码所示,触发 resize 事件。
获取 DOM 元素新的宽度的代码正好位于 170 行,这行代码访问了元素属性 offsetWidth.
按照这篇文档的记录,下列属性或方法被 JavaScript 调用时,会迫使浏览器以同步的方式重新计算样式,触发布局操作即回流。
https://gist.github.com/paulirish/5d52fb081b3570c81e3a
浏览器这一机制本身没有什么问题,但是如果某个时间点,有大量的页面元素的上述属性被访问时,则很可能成为性能瓶颈。
回到本文的例子,因为我在 heavyFunction 函数里插入了一万个 div 元素,在 resize 检测时,这一万个元素的 offsetWidth 属性被访问,造成了浏览器回流的不断触发,最终导致了大量的 CPU 时间被花费在了 rendering 之上。
总结
本文介绍的使用 Chrome 开发者工具来分析 Web 应用性能问题的步骤,只是该工具使用技巧的冰山一角。后续有机会,Jerry 会继续把我在日常工作中学到的东西分享出来。
如果遇到 SAP UI5 应用出现性能问题,优先排查性能瓶颈是否由后台 API 造成。对于 SAP UI5 应用的前端实现来说,因为 UI5 已经为应用开发人员做出了良好的封装,因此绝大部分情形,应用开发人员无需手动操纵 DOM 元素和 CSS 样式,所以也不大可能出现类似本文 heavyFunction 函数里模拟的极端情况。
无论如果,如果你怀疑你的 SAP UI5 应用的前端也可能有性能问题,此时无需胡乱猜测,用本文介绍的步骤一试便知分晓。
感谢阅读。
Jerry 的 SAP UI5 专题
更多Jerry的原创文章,尽在:"汪子熙":