当前位置: 首页 > news >正文

基于一次应用卡死问题所做的前端性能评估与优化尝试

问题背景

在上个月,由于客户反馈客户端卡死现象但我们远程却难以复现此现象,于是我们组织了一次现场上门故障排查,并希望基于此次观察与优化,为客户端开发提供一些整体的优化升级。当然,在尝试过程中,也发现了不少适用于通用前端项目开发的一些故障排查与性能评估的手段,于是总结此文,希望可以对读者有所帮助。

需要注意,在本文中所指的客户端均指通过 electron 开发出来的客户端应用,所以本质上还是属于前端应用开发范畴,关于 electron 框架的介绍可以参考 https://www.electronjs.org/

现象复现

在客户那边,反馈过来的现象表现为“系统 CPU 资源未被占满,但客户端在操作一段时间后便卡死无法响应”。起初,我们根据用户的描述尝试在本地复现,但却没有收获;此外,由于客户的网络限制,也不方便频繁的远程连接以方便我们查看现场现象。

考虑到可能是机器部分配置较差(比如显卡)或者网络、机器自身运行软件过多等原因,而我们的开发机器由于要支持本地编译与调试,一般都是顶配机器,于是我们尝试让本机变慢,以模拟复现其现象,简单来看,存在如下几个思路:

  1. 卡死/卡顿复现:最好在虚拟机中操作,虚拟机本身分配资源相对主机较少,再加上 chrome devtools 配置增加延时 throttle 时长,比如500ms;电脑中再开启几个占用 CPU 性能的软件,比如 vscode,firefox 等等,可以一定程度上模拟卡顿现象,不一定稳定复现卡死现象;
  2. 操作卡顿复现:通过频繁的交互操作,制造同时多个请求并发发出的现象,加上 performance 录制,可以一定程度加重渲染进程的负担,以模拟操作卡顿现象;

定位问题

来到客户现场,作为首要尝试,当然是通过 topnetstat 或者 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 stoppropagationstopimmediatepropagation 的用途区别,可别用错了。关于此细节可以参考回答 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。此外,我们还提到了组件中包含过多闭包的问题,以及右键菜单卡顿问题的排查。

由于客户端需求迭代过快,在前端技术上没有做较多的数据监控、性能评估等建设,这都对我们评估用户体验与定位问题产生了影响;此外,由于生产工具链的不完善,在生产环境进行定位与调试都给我们带来了比较大的挑战与时间消耗,这也会是我们持续要跟进与解决的一些开发链路的效率提升工作之一。

通过这些优化,我们希望能够解决客户端卡死问题并改善卡顿现象,并提高用户的使用体验。当然,从具体效果上来看,我们确实在如下两个方面进行了改善:

  1. 交互性能上,问题页面在切换时,即便将 CPU 降低配置 4x 情况下也再无出现卡死现象,卡顿现象有减轻趋势;
  2. 渲染效率上,从数据上看,频繁出现的 500ms-700ms long task 已减为当前观察范围内没有超过 300ms 的 long task,代码执行效率上有较大提升;

以下为优化后效果采样图:

简要总结

通过分析与优化尝试,我们解决了客户端卡死问题,并改善了卡顿现象,但其中暴露出一些编程规范与用法不够优雅的问题还需要在日常中持续完善,这也是这次优化未尽事宜,需要在未来不断排期以彻底解决。

当然,此中涉及到的一些调试与问题定位方法,也不仅局限于客户端的问题排查,而是通用 Web 应用性能评估时调试可以用到的手段,而更深入的研究则要开始涉猎到框架代码等内部函数调用的地方了,这也是本文未涉及部分,有待后续继续研究与定位。


原文地址 基于一次应用卡死问题所做的前端性能评估与优化尝试

相关文章:

基于一次应用卡死问题所做的前端性能评估与优化尝试

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

JVM(上)

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

【js】js 异步机制详解 Generator / Async / Promise

三种语法功能放在一起&#xff0c;是因为他们都有相似特点&#xff1a; 维护某种状态在未来恢复状态并执行 本文重点回答以下几个问题&#xff1a; 为什么 Generator 和 Async 函数的 代码执行流 都可以简化成树形结构&#xff1f;async 函数为什么返回一个 promise&#xf…...

【动态规划】【数学】【C++算法】805 数组的均值分割

作者推荐 【动态规划】【数学】【C算法】18赛车 本文涉及知识点 动态规划 数学 805 数组的均值分割 给定你一个整数数组 nums 我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中&#xff0c;使得 A 数组和 B 数组不为空&#xff0c;并且 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类&#xff0c;如上Player模型定义了一个整形…...

一个golang小白使用vscode搭建Ununtu20.04下的go开发环境

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

【复现】Hytec Inter HWL 2511 SS路由器RCE漏洞_25

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

Kafka系列(四)

本文接kafka三&#xff0c;代码实践kafkaStream的应用&#xff0c;用来完成流式计算。 kafkastream 关于流式计算也就是实时处理&#xff0c;无时间概念边界的处理一些数据。想要更有性价比地和java程序进行结合&#xff0c;因此了解了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 这个领域&#xff0c;通常训练一个业务模型的难点并不在于算法的选择&#xff0c;而在于前期的数据清理和特征工程这些纷繁复杂的工作&#xff0c;训练过程中的问题在于参数的反复迭代优化。 AutoML 是 Azure Databricks 的一项功能&#xff0c;它自动的对…...

数学建模实战Matlab绘图

二维曲线、散点图 绘图命令&#xff1a;plot(x,y,’line specifiers’,’PropertyName’,PropertyValue) 例子&#xff1a;绘图表示年收入与年份的关系 ‘--r*’:--设置线型&#xff1b;r:设置颜色为红色&#xff1b;*节点型号 ‘linewidth’&#xff1a;设置线宽&#xff1…...

TypeError the JSON object must be str, bytes or bytearray, not ‘list‘

在使用python的jason库时&#xff0c;偶然碰到以下问题 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社区星友提问&#xff1a;请教星主和各位大佬&#xff0c;对于一个模块如果不加干预工具会让inst挤成一团&#xff0c;后面eco修时序就没有空间了。如果全都加instPadding会导致面积不够overlap&#xff0c;大家一般怎么处理这种问题&#xff1f; 在数字IC后端设计实现中…...

产品经理与产品运营的区别和联系

一、两者的职责区别 产品经理的目的&#xff1a;是创造有价值的产品 产品运营的目的&#xff1a;是让产品能有效的发挥出它应有的价值 二、两者的工作内容区别产品经理的工作内容 产品的经理的目的是创造有价值的产品&#xff0c;因此产品经理的所有工作都是围绕着&#xf…...

CMU15-445-Spring-2023-分布式DBMS初探(lec21-24)

Lecture #21_ Introduction to Distributed Databases Distributed DBMSs 分布式 DBMS 将单个逻辑数据库划分为多个物理资源。应用程序&#xff08;通常&#xff09;并不知道数据被分割在不同的硬件上。系统依靠单节点 DBMS 的技术和算法来支持分布式环境中的事务处理和查询执…...

Arch linux 安装

Arch linux 安装 介绍下载制作iSO启动盘安装arch linux设置字体连接互联网 安装过程磁盘分区设置设置镜像源设置引导文件挂载点安装base等基础软件生成fatab文件更改时区更改编码、语言更改编码更改语言 用户管理设置root密码新建普通用户 安装grub启动网络服务/GDM查看系统网络…...

最新ChatGPT/GPT4科研应用与AI绘图及论文高效写作

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

【leetcode】移除元素

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

Spring Boot整合Redis的高效数据缓存实践

引言 在现代Web应用开发中&#xff0c;数据缓存是提高系统性能和响应速度的关键。Redis作为一种高性能的缓存和数据存储解决方案&#xff0c;被广泛应用于各种场景。本文将研究如何使用Spring Boot整合Redis&#xff0c;通过这个强大的缓存工具提高应用的性能和可伸缩性。 整合…...

FastApi-参数接收的正确使用(2)

前言 本文是该专栏的第2篇,后面会持续分享FastApi以及项目实战的各种干货知识,值得关注。 本文重点介绍,在使用FastApi使用“参数接收”时遇到的三种类型“路径参数”,“查询参数”,“请求体”的相关问题以及相应的解决方案。 具体详细知识点,跟着笔者直接往下看正文。…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...