基于一次应用卡死问题所做的前端性能评估与优化尝试
问题背景
在上个月,由于客户反馈客户端卡死现象但我们远程却难以复现此现象,于是我们组织了一次现场上门故障排查,并希望基于此次观察与优化,为客户端开发提供一些整体的优化升级。当然,在尝试过程中,也发现了不少适用于通用前端项目开发的一些故障排查与性能评估的手段,于是总结此文,希望可以对读者有所帮助。
需要注意,在本文中所指的客户端均指通过 electron 开发出来的客户端应用,所以本质上还是属于前端应用开发范畴,关于 electron 框架的介绍可以参考 https://www.electronjs.org/
现象复现
在客户那边,反馈过来的现象表现为“系统 CPU 资源未被占满,但客户端在操作一段时间后便卡死无法响应”。起初,我们根据用户的描述尝试在本地复现,但却没有收获;此外,由于客户的网络限制,也不方便频繁的远程连接以方便我们查看现场现象。
考虑到可能是机器部分配置较差(比如显卡)或者网络、机器自身运行软件过多等原因,而我们的开发机器由于要支持本地编译与调试,一般都是顶配机器,于是我们尝试让本机变慢,以模拟复现其现象,简单来看,存在如下几个思路:
- 卡死/卡顿复现:最好在虚拟机中操作,虚拟机本身分配资源相对主机较少,再加上 chrome devtools 配置增加延时 throttle 时长,比如500ms;电脑中再开启几个占用 CPU 性能的软件,比如 vscode,firefox 等等,可以一定程度上模拟卡顿现象,不一定稳定复现卡死现象;
- 操作卡顿复现:通过频繁的交互操作,制造同时多个请求并发发出的现象,加上 performance 录制,可以一定程度加重渲染进程的负担,以模拟操作卡顿现象;
定位问题
来到客户现场,作为首要尝试,当然是通过 top
、netstat
或者 cat /proc/cpuinfo
等命令来查看系统的 CPU、内存与网络的运行状态,但不出所料,这些信息在当前看来并没有太大异常。
由于从系统本身的一些状态上没能找到突破口,我们将目光转向客户端本身,希望在更小的范围内定位问题所在。通过 devtools 查看 netowork、performance 以及 DOM 渲染状态,我们只能发现貌似有些响应耗时过长的接口调用以及较长时长的 long task 任务,这当然需要我们进一步排查。
说到调试排查,首推的当然是 console.log 大法,为了让 log 打印复用,一个简单的技巧是写一个 HOC,以节省在每个地方都写一遍 debug log 的代码:
export const debugRender = <T=any>(BaseComponent: FC<T>) => (props:any) => {console.log(`Rendering ${BaseComponent.name} at ${performance.now()}`);return <BaseComponent {...props} />;
}
通过添加一些基于经验的断点信息打印,我们发现一些 Modal/Drawer 的显示/隐藏会较为明显的加重页面卡顿甚至到卡死现象上,通过排查代码实现以及查看对应 UI 库的 API 实现,会发现其中 Modal/Drawer 等组件上在隐藏时触发了其对应 DOM 节点的卸载,而在显示时又会重新渲染与插入,由于这些任务都需要在浏览器的渲染进程执行,而当 DOM 节点过多时频繁的节点装载与卸载便会对页面渲染效率产生影响。
于是,第一步便是定位到主要的几个组件,避免其在隐藏时执行 DOM 卸载(保留节点),通过这一步改变,我们直接消除了卡死现象。
部分优化尝试
为了更好的模拟卡顿现象,我们可以通过 chrome devtools 中 performance tab 中的 CPU throttling 配置来模拟卡顿:
在 Windows 高配版机器上,我们先将 CPU 降低配置 4x 情况,然后录制一段操作,从下图中可以看出有明显的任务执行耗时过长 & CPU 占用过高的现象:
以耗时最长的任务中占用时间最长的活动为例,我们搜索一下该关键词可以查到一个讨论 https://stackoverflow.com/questions/39916356/reacterrorutils-invokeguardedcallback-in-react-fires-event-repeatedly-in-ie-brow,简单来说,我们可以尝试优化点击事件不进行冒泡来减少事件的触发,例如:
event.stopPropagation();
通过优化该事件,我们可以一定程度上对事件在 DOM 上的传递 & 调用进行优化,但说到交互事件模型,我们在实际优化尝试时,也需要对 Web API 有些了解,以防用错 Web API 而南辕北辙,比如一个常见的面试题就是对比 Event 上暴露的两个 API stoppropagation
与 stopimmediatepropagation
的用途区别,可别用错了。关于此细节可以参考回答 https://stackoverflow.com/questions/5299740/stoppropagation-vs-stopimmediatepropagation
但假如我们需要针对不同事件切换不同的 API 该怎么办呢,这里可以简单写个函数封装一下,再加个类型守卫来实现,比如如下的伪代码通过传入一个点击回调事件,而后在实际事件触发时通过判断 Event 类型从而调用不同 API 以达到优化效果:
const isMouseEvent = (event: Event | MouseEvent): event is MouseEvent => 'stopImmediatePropagation' in eventexport const stopPropagationWrapper = (handleClick: Func) => (event: Event | MouseEvent) => {if (isMouseEvent(event)) {event.stopImmediatePropagation();} else {event.stopPropagation();}handleClick(event);
};
我们继续针对卡顿问题的调用情况进行梳理。从录制的执行队列中选取较长的一个 long task 进行分析,可以看到在模拟卡顿时排名靠前四的调用任务分别如下:
其中 fsync 函数调用时间占第一,而拆分 fsync 的活动调用可以看到主要调用了 fsyncSync:
此处未对 fsync 进一步分析以确定优化策略,但对于 fsync 的作用可以参考如下一段描述:
fsync 函数只对由文件描述符 filedes 指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync 可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。
这说明应用中有可能有数据库读写操作,也可能有文件读写操作,所以如果要进一步优化的话可以从这个方面展开,囿于时间限制,我们继续探索可行的快捷优化方案。
在最初解决卡死问题时,我们看到了过多的 DOM 卸载/挂载现象,但回到前端框架本身,我们也可以用一些常规的手段来减少组件不必要的 rerender,这些方案通常通过仔细阅读 React 文档便可以略知一二,比如在必要的地方增加 memo 以减少不必要的渲染执行,一个示例代码如下:
import {FC,memo,
} from 'react';const Detail: FC = ({}) => {return (<div>Detail</div>);
}export default memo(Detail);
此外,还有什么写法可能会影响 Web 应用的性能呢?闭包。
我们检查了客户端代码仓库里的两个列表文件,发现其中组件包含过多闭包变量,大多数写法是在一些函数定义中直接从上层作用域引用了一些变量进行操作,而不是通过参数传入函数,这样的数据/函数在使用后无法及时释放内存空间,可能会对内存存在持续占用的现象,因此,这也是优化的方向之一。
后续可能的优化空间
在一些 long task 任务的分析中,我们还可以具体定位到代码来进行优化,这里再举一个例子。
通过录制卡死情况下的堆栈调用情况,可以发现有一个 2.7s 任务中包含很多活动,如 Minor GC、react event、fsync、ReactElement 等等,其中 mergeProps 函数调用耗时250+ms。
针对这些函数调用,有些可能是 React 内部实现 API,有些可能是 UI 库 API,所以要想一一优化,也需要逐个分析,看是优化代码的调用与响应方式,还是合并组件 props 的传递与调用。
此外,通过监控 layers 变化情况,也会发现一些 slow scroll rects,这在 chrome 中都会通过红色区域以标注出来,通过定位这些在滚动中可能会造成缓慢的区域并检查代码,也有提升应用性能的可能性,因此,也是优化方向之一。
比如针对我们的场景,通过调整 layer 布局,可以看到虽然 layer 层级很多,但是主要的 slow scroll rects 区域还是集中在主内容区,即分页列表本身。
Electron 注意事项
本来,为了可以针对这些数据进行持续的分析,想从 performance 中将数据下载下来,以便之后有空时继续调试,但由于 Electron 的某些限制或者说是错误,我们目前无法保存 performance tab 下的性能数据到本地以便进行更深入的分析和查看。如果有涉及到 electron 开发的场景,需要注意下这个问题。问题现象详见 issue https://github.com/electron/electron/issues/39818
优化效果
为了提高客户端的性能和用户体验,我们进行了一系列的优化措施。首先,我们分析了卡死现象,包括客户端出现卡死时的 CPU 占用率/JS 堆栈/DOM 节点数情况、虚拟机运行状态等。然后,我们尝试了一些优化措施,如去除Modal/Drawer的 unmountOnExit 配置等。接下来,我们梳理了卡顿问题调用情况,分析了排名靠前的四个调用任务。为了减少组件不必要的rerender,我们在必要的地方增加了 memo。此外,我们还提到了组件中包含过多闭包的问题,以及右键菜单卡顿问题的排查。
由于客户端需求迭代过快,在前端技术上没有做较多的数据监控、性能评估等建设,这都对我们评估用户体验与定位问题产生了影响;此外,由于生产工具链的不完善,在生产环境进行定位与调试都给我们带来了比较大的挑战与时间消耗,这也会是我们持续要跟进与解决的一些开发链路的效率提升工作之一。
通过这些优化,我们希望能够解决客户端卡死问题并改善卡顿现象,并提高用户的使用体验。当然,从具体效果上来看,我们确实在如下两个方面进行了改善:
- 交互性能上,问题页面在切换时,即便将 CPU 降低配置 4x 情况下也再无出现卡死现象,卡顿现象有减轻趋势;
- 渲染效率上,从数据上看,频繁出现的 500ms-700ms long task 已减为当前观察范围内没有超过 300ms 的 long task,代码执行效率上有较大提升;
以下为优化后效果采样图:
简要总结
通过分析与优化尝试,我们解决了客户端卡死问题,并改善了卡顿现象,但其中暴露出一些编程规范与用法不够优雅的问题还需要在日常中持续完善,这也是这次优化未尽事宜,需要在未来不断排期以彻底解决。
当然,此中涉及到的一些调试与问题定位方法,也不仅局限于客户端的问题排查,而是通用 Web 应用性能评估时调试可以用到的手段,而更深入的研究则要开始涉猎到框架代码等内部函数调用的地方了,这也是本文未涉及部分,有待后续继续研究与定位。
原文地址 基于一次应用卡死问题所做的前端性能评估与优化尝试
相关文章:

基于一次应用卡死问题所做的前端性能评估与优化尝试
问题背景 在上个月,由于客户反馈客户端卡死现象但我们远程却难以复现此现象,于是我们组织了一次现场上门故障排查,并希望基于此次观察与优化,为客户端开发提供一些整体的优化升级。当然,在尝试过程中,也发…...

JVM(上)
目录 一、JVM概述 一、JVM作用 二、JVM整体组成部分 二、JVM结构-类加载 一、类加载子系统概述 二、类加载过程 1.加载 2.链接 3.初始化(类加载过程中的初始化) 三、类加载器分类 大致分两类: 细致分类: 四、双亲委派机制 五、打…...

【js】js 异步机制详解 Generator / Async / Promise
三种语法功能放在一起,是因为他们都有相似特点: 维护某种状态在未来恢复状态并执行 本文重点回答以下几个问题: 为什么 Generator 和 Async 函数的 代码执行流 都可以简化成树形结构?async 函数为什么返回一个 promise…...

【动态规划】【数学】【C++算法】805 数组的均值分割
作者推荐 【动态规划】【数学】【C算法】18赛车 本文涉及知识点 动态规划 数学 805 数组的均值分割 给定你一个整数数组 nums 我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中,使得 A 数组和 B 数组不为空,并且 average(A) average(B)…...
Django笔记(五):模型models
首 Django中的模型对应数据库中的一张表格。 定义模型 player.py from django.db import modelsclass Player(models.Model):idx models.IntegerField(uniqueTrue)def __str__(self):return str(self.id)每个模型需要继承models类,如上Player模型定义了一个整形…...

一个golang小白使用vscode搭建Ununtu20.04下的go开发环境
文章目录 前言搭建go环境下载go安装包解压go压缩包完成安装配置环境变量编写一个helloword程序 安装VSCode插件安装智能提示插件安装go依赖包修改代理并重新安装依赖包 go.mod 和 go.workgo.modgo.work小试一下go.work 总结 前言 先交代一下背景,距离正式接触golan…...

【复现】Hytec Inter HWL 2511 SS路由器RCE漏洞_25
目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一: 四.修复建议: 五. 搜索语法: 六.免责声明 一.概述 Hytec Inter HWL 2511 SS是日本Hytec Inter 公司的一款工业级 LTE 路由器,可用于远程数据传输,例如收集传…...

Kafka系列(四)
本文接kafka三,代码实践kafkaStream的应用,用来完成流式计算。 kafkastream 关于流式计算也就是实时处理,无时间概念边界的处理一些数据。想要更有性价比地和java程序进行结合,因此了解了kafka。但是本人阅读了kafka地官网&#…...

【Linux学习】进程信号
目录 十七.进程信号 导言 17.1 linux中的信号列表 17.2 标准信号与实时信号 17.3 信号的产生 17.3.1 通过终端按键产生信号 17.3.2 调用系统函数产生信号 17.3.3 软件条件产生信号 17.3.4 硬件异常产生信号 17.3.5 【补充】核心转储 Core Dump 17.4 信号的阻塞 17.4.1 信号相关…...

机器学习没那么难,Azure AutoML帮你简单3步实现自动化模型训练
在Machine Learning 这个领域,通常训练一个业务模型的难点并不在于算法的选择,而在于前期的数据清理和特征工程这些纷繁复杂的工作,训练过程中的问题在于参数的反复迭代优化。 AutoML 是 Azure Databricks 的一项功能,它自动的对…...

数学建模实战Matlab绘图
二维曲线、散点图 绘图命令:plot(x,y,’line specifiers’,’PropertyName’,PropertyValue) 例子:绘图表示年收入与年份的关系 ‘--r*’:--设置线型;r:设置颜色为红色;*节点型号 ‘linewidth’:设置线宽࿱…...
TypeError the JSON object must be str, bytes or bytearray, not ‘list‘
在使用python的jason库时,偶然碰到以下问题 TypeError: the JSON object must be str, bytes or bytearray, not ‘list’ 通过如下代码可复现问题 >>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> import json >>> ra json.loads(a) Trac…...

数字IC后端设计实现 | PR工具中到底应该如何控制density和congestion?(ICC2Innovus)
吾爱IC社区星友提问:请教星主和各位大佬,对于一个模块如果不加干预工具会让inst挤成一团,后面eco修时序就没有空间了。如果全都加instPadding会导致面积不够overlap,大家一般怎么处理这种问题? 在数字IC后端设计实现中…...
产品经理与产品运营的区别和联系
一、两者的职责区别 产品经理的目的:是创造有价值的产品 产品运营的目的:是让产品能有效的发挥出它应有的价值 二、两者的工作内容区别产品经理的工作内容 产品的经理的目的是创造有价值的产品,因此产品经理的所有工作都是围绕着…...

CMU15-445-Spring-2023-分布式DBMS初探(lec21-24)
Lecture #21_ Introduction to Distributed Databases Distributed DBMSs 分布式 DBMS 将单个逻辑数据库划分为多个物理资源。应用程序(通常)并不知道数据被分割在不同的硬件上。系统依靠单节点 DBMS 的技术和算法来支持分布式环境中的事务处理和查询执…...
Arch linux 安装
Arch linux 安装 介绍下载制作iSO启动盘安装arch linux设置字体连接互联网 安装过程磁盘分区设置设置镜像源设置引导文件挂载点安装base等基础软件生成fatab文件更改时区更改编码、语言更改编码更改语言 用户管理设置root密码新建普通用户 安装grub启动网络服务/GDM查看系统网络…...

最新ChatGPT/GPT4科研应用与AI绘图及论文高效写作
详情点击链接:最新ChatGPT/GPT4科研应用与AI绘图及论文高效写作 一OpenAI 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析,AI画图,图像识别,文档API 3.GPT Store 4.从0到1创建自己的GPT应用 5. 模型Gemini以及大模型Clau…...

【leetcode】移除元素
大家好,我是苏貝,本篇博客带大家刷题,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 一.暴力求解法二.使用额外数组三.原地修改数组 点击查看题目 一.暴力求解法 若我们不考虑时间复杂度…...

Spring Boot整合Redis的高效数据缓存实践
引言 在现代Web应用开发中,数据缓存是提高系统性能和响应速度的关键。Redis作为一种高性能的缓存和数据存储解决方案,被广泛应用于各种场景。本文将研究如何使用Spring Boot整合Redis,通过这个强大的缓存工具提高应用的性能和可伸缩性。 整合…...
FastApi-参数接收的正确使用(2)
前言 本文是该专栏的第2篇,后面会持续分享FastApi以及项目实战的各种干货知识,值得关注。 本文重点介绍,在使用FastApi使用“参数接收”时遇到的三种类型“路径参数”,“查询参数”,“请求体”的相关问题以及相应的解决方案。 具体详细知识点,跟着笔者直接往下看正文。…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...