记一次 React 开源甘特图组件的性能优化,已合入 PR!

2023-05-16

背景

公司项目最近用到甘特图功能,于是集成了一款开源的甘特图插件。

甘特图的主要作用是项目管理,可以用图示的方式通过活动列表和时间刻度形象地表示出任何特定项目的活动顺序与持续时间,如下图

acd81b51688ca51cea422084d070fb79.png
image.png

玩过甘特图的同学都知道,甘特图的前端实现基本靠绘画。而绘画是对前端的开发和性能要求非常高的一项技术。而频繁的交互操作,也会导致开发的性能要求进一步严格。

现象

其基础现象很简单。当我拖动甘特图的视图区域时,明显感受到卡顿和拖影。各位同学都明白,涉及到绘画相关的动画操作,要60fps才能够到顺滑的阶段。30fps勉强卡顿,20fps就卡顿拖影严重了。

于是我采用了 Frame Rendering Stats 工具先进行肉眼观察帧率数值。他的主要作用除了观察当前页面操作的fps数值外,还可以监控gpu的内存用量。当然这个工具的位置也很容易找。就在 Chrome Devtools 的 Rendering 选项中,勾选开启即可

ecfb80449b9d7608bd0568752d43fd39.png
image.png

当我使用工具进行 fps 的观察,同时视图区域进行稳定匀速的滑动时,能够感受到明显的卡顿和拖影。其检测数值最高仅有31fps,最低有26fps,卡顿的级别基本上属于严重卡顿。如果换一台低端一点的设备,那么其展示效果肯定无法想象。

fe839f5ef1531741c101471f27a357e0.png
image.png

分析

既然我们发现了问题,那么就分析下问题到底出在哪里。接着打开 Performance 工具并开始录制,录制的同时对视图区域进行稳定匀速的滑动,滑动几秒后停止录制,拿到一份这样的分析报告:

e86f12228570d3239d5586781e5558b1.png
image.png

甘特图插件和主要技术栈都是react。在 react 16最新的fiber架构中,为了让响应可以更快的得到反馈,拆分了子任务。而根据一般的显示器刷新率(60hz)和目前的浏览器所支持的最高刷新率来算,平均下来每一帧的任务时长一般只有16.6ms。当单帧任务时长超过16.6ms时,就会产生卡顿和掉帧。

但是根据分析情况来看,上图滚动时产生的任务绝大多数都大于40ms,甚至还会产生longtask(Chrome官方对longtask的定义是大于50ms,即20fps)。所以接着展开来看,看看单任务中到底是哪些事件导致的执行时间长。

接着点开其中一个任务,放大详情。可以看到selftime(自身执行时间)排名第一的是一个匿名函数。继续点开右侧的代码堆栈,去看看哪行代码执行时间比较长。

20d0957461ce29b1e88163049224537c.png
image.png

点开后,会自动帮我们跳转到 Devtools 中的 source 模块,还会将代码的执行时间标在函数的左侧。从下文可以分析,第74行的 toLocaleDateString 的耗时非常严重。因为函数组件/类组件的渲染生成是同步的,所以耗时长会拖慢 render 的效率,进而拖慢整体的帧率。

fbb1eee81119083b7bf606adf6afb96f.png
image.png

时区转换的锅

Date.prototype.toLocaleDateString() 的作用是对不同语言的时间文本进行转换。例如

const event = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
console.log(event.toLocaleDateString('de-DE', options));
// expected output: Donnerstag, 20. Dezember 2012
console.log(event.toLocaleDateString('ar-EG', options));
// expected output: الخميس، ٢٠ ديسمبر، ٢٠١٢
console.log(event.toLocaleDateString(undefined, options));
// expected output: Thursday, December 20, 2012 (varies according to default locale)

但是这样一个看起来人畜无害的方法,怎么会耗时这么长呢?

鉴于直接翻看v8的这部分源码比较硬核,我们选择去查看 toLocaleDateString 的 polyfill —— formatjs。这个库一直作为 Date 方面的国际化polyfill存在着,包括时区国际化和时间文本国际化。

我们找到 formatjs 中的 packages/intl-datetimeformat/src/to_locale_string.ts 中的 toLocaleString 方法。这个方法创造了一个时间格式化对象:

cd072c2761efa673942bf947795d7471.png
image.png

继续跟踪 DateTimeFormat 类的实现,可以看到有一个叫做 localeData 的变量。这个变量就是我们做国际化时的各国语言文本内容。同样上面有一个叫 tzData 变量,是时区数据库的内容:

318882058a4da3ee2b27b3543bbf232f.png
image.png

接着一路跟踪,会发现 ResolveLocale 方法是处理当前选中时区的核心方法。在那里面,所有的国际化文本都会经过运算筛选,再与当前选中的语言文本进行匹配(尤其是下图当中这种 indexOf 高耗时方法)

0289fc5e99a0490a074e38c37a7481f3.png
image.png

解决方案

对于此类调用耗时问题,唯一的解决就是对现有的执行结果加缓存memo。方案很简单:将时间转化为时间戳作为缓存的key,存入缓存,后续直接从缓存读取即可:

dd29f18d0908f78175135a7f11caf528.png
image.png

优化后,我们再次用performance进行分析。发现不仅fps有肉眼和数值的显著提升,且longtask也不再存在,平均任务耗时被压缩到了23ms。基本上实现了流畅,解决了卡顿问题。

95844a8019946e319c79882c4e9074c9.png
image.png

但,我们还要继续解决 toLocaleDateString 的兄弟api:Intl.DateTimeFormat

关于 Intl.DateTimeFormat

Intl.DateTimeFormat 是一个比较新的时间格式化api。他与 toLocaleDateString 在使用上最大的不同时,支持对任意的date对象进行format,api设计上偏向构造器,更加利于缓存设计。例如用法:

console.log(new Intl.DateTimeFormat('en-US').format(date));
// expected output: "12/20/2020"
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long' }).format(date));
// Expected output "Sunday, 20 December 2020 at 14:23:16 GMT+11"

同样,在对上面的 toLocaleDateString 进行性能优化完毕后,排在react耗时后面的 Intl.DateTimeFormat 也值得处理。继续查看代码耗时:

b1b7372051cfc8db0a67b098adfdd0f7.png
image.png
458067e42097a6bc698008ac4e421c01.png
image.png

发现此方法的耗时也不低:7.1ms,有提升空间。而此方法的polyfill实现,也和上面的 toLocaleDateString 一致,都是实例化 DateTimeFormat 对象才可以用。只不过区别是一个手动实例化,一个帮你实例化:

d95c9fdf2ef54837977e1979f9f9cf77.png
image.png

那我们就继续对 Intl.DateTimeFormat 增加缓存。

最终优化结果

按照对 toLocaleDateString 的优化思路,我们只需要对 Intl.DateTimeFormat 实例进行优化即可。依然是做缓存,只不过 key 换成了地区 + 转化选项这唯一的参数:

74bc307bf4d17d57744668d4209d9967.png
image.png

优化完毕后,我们再次采集一份performance样本。

通过检测,fps已经达到了最低45,最高50的数据。基本上实现流畅(因Devtools开启状态下也耗性能,实际使用帧率比这个高)。相比优化前,提升了61%。long task消失不存在

3fd55d86525f3b90c869a99979ecc681.png
image.png

结尾

当然,这份优化历程只是个初步优化。可以看到,虽然单个任务的耗时有所大幅度下降,但是还有提升空间存在。要尽量低于16.6ms才能够实现完全流畅。

总结一下:尽量采用 Intl.DateTimeFormat 来替代 toLocaleDateString,并对构造器进行缓存提升性能。在其他国际化的场景(例如数字等)也要注意这一点

此外,这份性能优化的方案已经提交给了上游开源项目,并在8.15日已经合并进仓库:https://github.com/MaTeMaTuK/gantt-task-react/pull/19

41280a572afac476d19297c0eed49d3a.png
image
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

记一次 React 开源甘特图组件的性能优化,已合入 PR! 的相关文章

  • 小坑整理

    for range坑 问题描述 range循环的时候 xff0c 取赋值的地址 xff0c 然后保存到map中 xff0c 结果是什么样子 xff1f 这里先回想下变量是什么时候才申请内存 xff1f 只有赋值给新的变量时 xff0c 内存
  • go操作es

    TOC 前言 xff1a elasticsearch 是一个基于Lucene构建的开源的 分布式 restful接口的全文搜索引擎 es还是一个分布式的文档数据库 xff0c 其中每个字段均可被索引 xff0c 而且每个字段的数据均可被搜索
  • 9-gin使用websocket

    toc gin使用websocket Gin 框架默认不支持 websocket xff0c 可以使用 github com gorilla websocket 实现 Talk is cheap Show me the code xff0c
  • 1. 准备环境

    硬件资源 mac 12 2 1 golang 1 15 3 minikube v1 25 1 prometheus operator 0 35 1 下载地址 xff1a https github com prometheus operato
  • pprof查docker内存占用

    https zhuanlan zhihu com p 363584053 https blog csdn net u010278923 article details 78774914
  • oklog run包使用

    oklog run 包介绍 oklog run 包非常简单 xff0c 只有一个类型 xff0c 两个方法 xff0c 共 60 行代码 其中 Group 是一组 actor xff0c 通过调用 Add 方法将 actor 添加到 Gro
  • windows上IIS实现https,配置ssl证书

    windows2003 系统IIS6安装ssl证书 http bbs qcloud com thread 4793 1 1 html windows2012实现IIS7的https 1 申请证书 xff0c 这里申请腾讯云的证书 1 xff
  • Ubuntu 14.04升级到Ubuntu 16.04(桌面版和字符版)

    下面记录了从Ubuntu 14 04 15 10升级到Ubuntu 16 04的步骤 桌面版 在升级系统之前 xff0c 我们先更新一下系统 打开终端执行如下命令 xff1a sudo apt span class hljs attribu
  • 编写使用systemctl启动服务脚本

    CentOS 7的服务systemctl脚本存放在 xff1a usr lib systemd xff0c 有系统 xff08 system xff09 和用户 xff08 user xff09 之分 xff0c 像需要开机不登陆就能运行的
  • python中print打印显示颜色

    显示颜色的格式 xff1a 033 显示方式 字体色 背景色m 033 0m 显示颜色的参数 xff1a 显示方式效果字体色背景色颜色描述0终端默认设置3040黑色1高亮显示3141红色4使用下划线3242绿色5闪烁3343黄色7反白显示3
  • ds1302时钟芯片工作原理引脚电路图及功能

    资源获取码 xff1a aaaa DS1302时钟实用仿真图 上面仿真图功能描述 xff1a 1 能读取DS1302数据 xff0c 并显示在数码管上面 2 带按键设置功能 xff0c 可以设置DS1302时分秒数据 3 带倒计时功能 xf
  • 吴恩达 DeepLearning 第一课第二周编程题目及作业(可免费下载资源)

    提示 作业里面会有需要用到的 Python 模块以及数据集 所以我下面再附上所需要的文件下载链接 xff0c 不把所有文件连同作业放一起打包好的目的是让第一次接触 Python 的人更多的了解 Python xff0c 万事开头难 xff0
  • linux centos7.6 关闭防火墙

    停止firewall root 64 lch software systemctl stop firewalld service 禁止firewall开机启动 root 64 lch software systemctl disable f
  • STM32之SHT30温湿度传感器驱动代码(程序稳定,清晰明了)

    第一部分 xff1a SHT30温湿度模块代码头文件 xff08 SHT30 h xff09 ifndef SHT30 H define SHT30 H include 34 delay h 34 include 34 sys h 34 i
  • 数据集(3):从0了解INRIA数据集

    该数据集分为两种格式 xff1a xff08 a xff09 具有对应注释文件的原始图像 xff08 b xff09 原始的负样本图像和标准化64 128像素的正样本图像 xff08 用于CVPR论文中的 xff09 根目录下虽然有六个文件
  • Ubuntu 登录界面,字体变大,输入密码出现循环登录问题

    出现这个问题 xff0c 很大可能就是显卡驱动掉了 xff0c 只需重新安装就好 参考 xff1a https blog csdn net qq 36427732 article details 80914653 一 下载显卡驱动 用一台电
  • virtualenv的使用

    安装 pip install virtualenv 使用 1 创建一个文件夹 xff0c 用来存放环境 xff0c 之后pip install 之类的东西都在里面 2 在该文件夹中建立该环境 virtualenv p usr bin pyt
  • LIF神经元介绍

    Integrate And Fire Models 基础知识 轴突 xff1a 动作电位 xff08 电位差形成电流 xff09 61 神经递质发放 61 脉冲产生 树突或细胞体 xff1a 神经递质的接受 61 产生内外膜电位差 xff0
  • 神经网络中Batch Size的理解

    直观的理解 xff1a Batch Size定义 xff1a 一次训练所选取的样本数 Batch Size的大小影响模型的优化程度和速度 同时其直接影响到GPU内存的使用情况 xff0c 假如你GPU内存不大 xff0c 该数值最好设置小一
  • Hessian矩阵

    转 xff1a 黑塞矩阵 xff08 Hessian Matrix xff09 转 xff1a Hessian矩阵以及在图像中的应用 黑塞矩阵 xff08 Hessian Matrix xff09 xff0c 是一个多元函数的二阶偏导数构成

随机推荐

  • 残差结构详解

    背景知识 xff1a 为什么要构建深层网络 xff1f 答 xff1a 认为神经网络的每一层分别对应于提取不同层次的特征信息 xff0c 有低层 xff0c 中层和高层 xff0c 而网络越深的时候 xff0c 提取到的不同层次的信息会越多
  • 第二章 Internet 地址结构

    第二章 Internet 地址结构 本文为 TCP IP 详解 学习笔记 文章目录 第二章 Internet 地址结构2 1 引言2 2 表示 IP 地址IPv4 地址IPv6 地址 2 3 基本的 IP 地址结构2 3 1 分类寻址2 3
  • 机器学习中数据不均衡问题(分类类别数据不均匀)

    在机器学习中 xff0c 我们经常会遇到类别数据分布不均衡问题 xff0c 即某类中含有很多数据 xff0c 而其他类别中的数据量很少 在这种情况下 使用传统机器学习算法开发的预测模型可能存在偏差和不准确 xff0c 造成上述的原因是 xf
  • EAST: An Efficient and Accurate Scene Text Detector翻译

    Abstract 用于场景文本检测的先前方法已经在各种基准测试中获得了良好的性能 然而 xff0c 在处理具有挑战性的场景时 xff0c 即使配备了深度神经网络模型 xff0c 它们通常也会达不到很好性能 xff0c 因为整体性能取决于管道
  • pytorch搭建网络结构

    记录pytorch怎么搭建网络 xff0c 看起来更舒服 首先定义一个block class myBlock nn module def init self in channel out channel xff1a super myBloc
  • ubuntu codename

    1 查看当前系统的codename lsb release a 2 历史版本codename Ubuntu 发布版本的官方名称是 Ubuntu X YY xff0c 其中 X 表示年份 xff08 减去2000 xff09 xff0c YY
  • xubuntu_18.04取消开机自动登录

    虽然开机自动登录听方便的 xff0c 但是总感觉不是那么的安全 xff0c 就把开机自动登录取消了 xff0c 下面是取消自动登录的方法 1 首先查一下自动登录的用户 grep R 34 autologin 34 etc lightdm 注
  • 双目相机与IMU联合标定

    前言 为了后面的视觉激光融合SLAM以及跑通VINS Fusion xff0c 需要标定双目相机和IMU得内参以及它们得外参 xff08 变换矩阵 xff09 准备工作 双目相机 xff1a ZED mIMU xff1a realsense
  • Python中画图时候的线类型

    在Python中用matplotlib画图的时候 xff0c 为了区分曲线的类型 xff0c 给曲线上面加一些标识或者颜色 以下是颜色和标识的汇总 颜色 xff08 color 简写为 c xff09 xff1a 蓝色 xff1a 39 b
  • Ubuntu20.04中,安装微信步骤总结

    先安装git xff08 如果已安装 xff0c 清忽略 xff09 sudo apt install git 检查git是否安装成功 xff08 如果已安装 xff0c 清忽略 xff09 git version 安装deepin win
  • touchgfx 浮点数显示

    最近在研究touchgfx xff0c 太懵了 xff0c 也就好久没有更新博文了 xff0c 很坑的一点 xff0c 我就想在屏幕上显示一个小数 xff0c 翻看了N多的博文 xff0c 没一个管用的 xff0c 这里分享一下我的方案 选
  • 浅谈专线(SD-WAN)

    存在就一定有存在的道理 xff0c 今天要说的网络知识点是专线 我们一步步来 xff0c 先说专线是什么 xff0c 然后说专线的特性 xff0c 最后说专线的未来 一 什么是专线 专线就是专门分的线路 xff0c 是运营商为企业客户分配的
  • Ubuntu18系统设置自定义分辨率1920*1080

    一般安装完unbuntu后会发现系统分辨率没有19201080 xff0c 需要手动自定义添加19201080分辨率 打开终端 xff0c 输入命令 xff1a xrandr xff0c 可以查看系统所有分辨率 输入命令生成显示 xff1a
  • 基于饿了么骨架屏方案,使用Chrome扩展程序生成网页骨架屏

    前言 之前写移动端项目的时候 xff0c 使用骨架屏来解决首屏渲染时出现短暂空白现象 xff0c 采用了就是饿了么page skeleton webpack plugin方法 但是page skeleton webpack plugin需要
  • 别瞎删package-lock.json了

    作者 xff1a wuwhs 原文 xff1a https segmentfault com a 1190000039684460 0 前言 看完本文 xff0c 你将从整体了解依赖版本锁定原理 xff0c package lock jso
  • Naive-UI,尤大推荐的Vue组件库

    前言 早上坐地铁上班的时候 xff0c 刷到推特推送了naive组件库有关的信息 点进去看了介绍 xff0c 觉得不错 xff0c 就来分享一下 组件库文档地址 xff1a www naiveui com 值得注意的是 xff0c vue作
  • 2021下半年最新前端求职面试指导(完整版)

    两周前 xff0c 学弟通过了阿里六轮面试 xff0c 顺利拿到了 Offer xff01 负责高德地图的前端部署 薪资待遇很不错 xff0c 30K 15薪 在此之前 xff0c 他在国营公司干了3 年 xff0c 这次回到大厂 xff0
  • 一文讲懂npm link

    前言 在本地开发npm模块的时候 xff0c 我们可以使用npm link命令 xff0c 将npm 模块链接到对应的运行项目中去 xff0c 方便地对模块进行调试和测试 用法 包链接是一个两步过程 xff1a 1 为依赖项创建全局软链np
  • Typescript中的extends关键字

    前言 extends关键字在TS编程中出现的频率挺高的 xff0c 而且不同场景下代表的含义不一样 xff0c 特此总结一下 xff1a 表示继承 拓展的含义表示约束的含义表示分配的含义 基本使用 extends是 ts 里一个很常见的关键
  • 记一次 React 开源甘特图组件的性能优化,已合入 PR!

    背景 公司项目最近用到甘特图功能 xff0c 于是集成了一款开源的甘特图插件 甘特图的主要作用是项目管理 xff0c 可以用图示的方式通过活动列表和时间刻度形象地表示出任何特定项目的活动顺序与持续时间 xff0c 如下图 image png