记一次线上性能问题排查和修复过程

1,707 阅读4分钟

img

天冷了,加衣服 ~

背景

最近公司级重要项目 CRM 收到坐席反馈说策略向导页面卡顿,严重影响了坐席的使用率和满意度,但曾经开发这块功能的同事早已经离职的离职,剩下的也不再负责这一块的业务,于是老大交给了我,让我务必尽快解决!

也算是一次临危受命了 ~

策略向导页面展示方式类似于脑图,用于帮助坐席在短时间内找到最精准的策略以响应用户需求。

一个策略大概长下面这样:

脑图展示

策略界面可以展示多个脑图,并通过 Tab 页签进行切换。

问题最直观的表现就是切换 Tab 页签尤其卡顿!

排查

按照坐席反馈的复现步骤,点击页面上的 【获取更多权限】 按钮将会展开所有节点,当展开之后来回切换脑图即可复现,轻则需要等待五六秒才能切换成功,重则直接将页面卡崩掉。复现之后,通过 Chrome DevTool 中的 Performance 面板捕捉切换的过程:

performance 面板

可以清晰的看到,当点击切换之后,较多的时间耗在了 show 方法上

展开具体的 Call Tree 看看

callTree

当我们展开后发现 init_nodes_size 中触发的 Layout 耗时较长,达到了 2919.8 ms,大概占用了整个时间的 65.2%。定位到代码中如下:

init_nodes_size

上面是 init_nodes_size 的具体代码,第一步中获取了元素的 view_data.elementclientWidthclientHeight,然后在第二步中设置了元素 view_data.calloutwidth 并且还在第三步再次获取了 view_data.calloutclientHeight,这一个函数就触发了两次重排,如果该函数在整个更新逻辑只执行一次倒不会有太明显的性能问题,但是通过调用栈向上查看,发现是 init_nodes 内部调用了 init_nodes_size,并且在同一时间内会循环每个节点执行一次 init_node_size,由于实际生产环境中节点数量非常大,因此该函数将会导致严重的性能下滑。

修复

既然找到了问题的根本原因,那就很好解决了。针对这里的问题,我对代码做了如下调整:

refactor

由于之前的代码在 init_nodes_size 最后还再次获取了 view_data.calloutclientHeight,但在获取之前并没有导致 clientHeight 变化的操作,因此将它提前,放到了上图第一个红框中,这样一来如果需要获取 view_data.calloutclientHeight 则会和上面获取 view_data.element 的宽高的代码一并执行,中途不会触发重排。

另外,在第二个红框中,我将之前修改 css 的代码封装到了临时的函数 upupdate的缩写)中并挂载到 node 上,这部分代码在执行完所有节点的 init_nodes_size 之后的第三个红框中批量触发,从而只触发一次重排即可完成整个脑图节点的更新。考虑到 up 函数实际上只会执行一次,当执行完成之后通过 delete 将它释放,从而避免潜在的内存溢出风险。

如果追求更加苛刻的性能体验,可以将需要修改的参数直接挂载到 node 上,然后在批量更新的时候直接修改对应元素节点的属性,从而可以节省执行 up 函数时调用栈切换的时间,但可读性不如目前的这种方式,并且在我看来那属于过度优化,毕竟即便是几千个函数上下文的切换按照 js 的执行速度几乎也是瞬间完成的,但这个思路很好,它来源于很早以前无意中看到的问题 for 循环和 forEach 哪个更快?,由于 forEach 会频繁切换上下文导致部分时间开销,因此相同的操作肯定是 for 更快。

经过本次优化,我们再看看 Performance 面板:

优化后的performance

如上图所示,优化之后,Layout 出现在 init_nodes 下,而 init_nodes_size 下不再存在 Layout,并且时间大幅缩减,从之前耗时的 2919.8 ms 减少到了 211.4 ms,耗时减少了约 93%

结语

以前对 重绘重排 对性能造成的影响一直只停留在理论层面,并没有实际深刻的体会,但这次让我认识到频繁的重排对性能是毁灭性的!

但好在经过对症下药之后,现在的策略切换已经显得非常流畅,但我总隐约感觉策略这里还有不少历史遗留的隐藏问题需要解决...

先告一段落了,不过不得不说,小小的改动能大大的提升了整个页面的性能,心中油然升起一种成就感满满的感觉!

疫情当前,大家加油!

image-20221223223059185