push竟比concat快上数百倍?记一个concat在十万级数据引发的性能问题

壹 ❀ 引
公司产品一直在做企业项目研发工具,所以我们自己当然也会用自己的产品去管理公司大小项目,但在此之前,项目管理体验上一直存在一个卡顿问题。比如我刚登录上账号,在项目里随便到处点点到处跳转页面,然后点击项目头部的搜索功能进行任意搜索,并成功跳转到搜索结果页后,再点击chrome
的回退按钮回到上个页面,就会遇到长达10S的页面卡顿,我的电脑是16G M1芯片都要卡这么久,像测试同学配置相对差一点的MAC,chrome
甚至会卡到直接失去响应,总而言之,如果客户碰巧也这么操作了,使用体验自然非常很好。
贰 ❀ Performance性能分析
好在chrome
已经提供了Performance
帮助我们分析页面加载性能瓶颈问题。F12
打开控制台,点击Performance
按钮就能看到如下界面,考虑到我的电脑配置较好,为了模拟低配置,更好的复现问题,所以我将CPU
选项选择为降低6倍性能6x slowdown
,另外,上面还有个Network
用于降网速,这个可用来模拟网络慢的情况,因为我这问题跟网络没啥关系,这里就不管了。

然后我还是按照上面说的操作,在项目里乱点一通,到处跳转,之后搜索,进入搜索页面,这时候就可以开启Performance
的录制功能,也就是上图那个黑色的圆形按钮,然后点击chrome
的回退按钮,卡顿几十秒后页面终于恢复正常,我们再点击录制按钮结束录制,少许片刻,于是我们得到了如下信息:

让我们把目光看向CPU的火焰图,从5000ms到60000ms这么长长的一段,接近55秒的时间内CPU使用都占了一半(黄色区域),那么性能问题自然出现在这55s之间。直接看一眼下面的统计报表,也能发现总共1.1min中,有62446ms在执行JS,而等待,渲染,重绘都在百ms,因此跟这些没太大关系。

我们拖拽鼠标,将分析范围选中为有问题的55S之间,可以看到是一个Task任务总共耗时是54秒,注意,这是一个长任务,也就是单一跑完这个任务就用了这么久。顺带解释下这段黄绿蓝区域的含义,横向表示这个任务耗时的长度,长度越长说明用时越久,纵向表示这个任务的调用栈,比如总任务名为Task,task下又分别包含了哪些任务呢?于是就有了纵向这样一列。

由于任务过长,我们不得不继续滚动鼠标,将选中范围精确到更小的执行粒子上,神奇的事情发生了,随着我选中范围越来越小,下面的调用栈名称居然就没变过,也就是说这么长的时间里,执行的JS过程就是如下这么一段,我们前面说了,纵向表示调用栈,而在最下的anonymous
下,存在无数个黄色的小段落,粗略来看,这个方法估计被执行了上千或者上万遍。

既然问题出在这,我们通过鼠标选中这个匿名函数,在Sunmary表报处我们就能看到这个函数所在的文件了,一个名为selector.js
的文件,点击进入,成功找到了可能有问题的代码:

看样子写这块代码的同学估计也猜到这里特别耗性能,所以才使用了memoize
做了缓存,只是没想到第一次缓存准备数据还是要等待几十秒。出于好奇,我在这段代码前后加了console.time
与console.timeEnd
,刷新,重新走一遍复现流程,看了眼控制台,人傻了...接近46秒,好奇看了眼这里的permissionRuleListMap
,居然有十几万的数据。

叁 ❀ 优化思路
怎么优化呢?其实文章标题已经给了答案了,就是一个小小的concat
引发的问题,这段代码第一获取了名为permissionRuleListMap
的所有key,然后遍历key,依次去取key对应的内容,再利用concat
将内容加入到新数组resultList
取,事实上这里都不需要做Object.keys
这部操作,毕竟Object.keys
也是一次遍历,十几万的数据跑一遍也需要时间,我们完完全全可以一遍遍历搞定,改为如下代码:
const resultList = [];
// 一次遍历,不用单独获取key
for (const key in permissionRuleListMap) {
// 用push取代concat
resultList.push(...permissionRuleListMap[key]);
}
return resultList;
重走上述流程,然后继续点回退,神奇的事情发生啦,看一眼控制台时间输出,现在只要几百毫秒了。

肆 ❀ 谨慎使用concat
为了更好的验证这两个方法的快慢,我们可以控制变量,声明一个包含10W个[1,2]
的数组,并将其复制到一个新数组中去,让我们来对比时间:
const arr = new Array(100000).fill([1, 2]);
let a1 = [];
let a2 = [];
// 使用concat
console.time('1');
for (let i = 0; i < arr.length; i++) {
a1 = a1.concat(arr[i]);
};
console.timeEnd('1');
// 使用push
console.time('2');
for (let i = 0; i < arr.length; i++) {
a2.push(...arr[i]);
}
console.timeEnd('2');

我们都知道,concat
是返回一个新数组,所以每次遍历,JS都需要新开一个内存,创建一个新数组,用于保存合并后的数组内容,所以遍历10W次,那么就需要重复开10W个内存,再将数组元素依次放进去,只要数组够大,它的时间只会要的更久。
相对而言push
就简单了,即便执行10W次,我们操作的始终是一个数组,每次我们都只是在这个数组上新加几个空位,用于依次存放新的数组元素而已,脑补一下这个过程,性能谁更优一目了然。
在Javascript Array.push is 945x faster than Array.concat一文中,也有阐述为啥push
比concat
快了接近945倍,这里我们只需要得知这个结论就好,总而言之,请谨慎使用concat
方法,如果你的数据量较大,就一定得留意这一点,那么本文就记录到这里啦。
push竟比concat快上数百倍?记一个concat在十万级数据引发的性能问题的更多相关文章
- 写markdown博客如何截图并快速上传到图床——记一个工具插件的实现
1. 背景 写博客有一个自己的图床是不错的选择,如果不借助工具,在markdown博客中添加图片的步骤如下: 截取图片,保存到本地(得来回点对话框,选择保存路径,选择文件类型,输入文件名). 上传到图 ...
- 写markdown博客如何将截图快速上传到图床——记一个工具插件的实现(windows版 开源)
打造一个上传图片到图床利器的插件(Mac版 开源)(2018-06-24 19:44) 更新于2018年2月 做了以下改动: 1.修复了一个bug,把服务器区域做成可配: 七牛有华北,华东,华南以及美 ...
- 数百个 HTML5 例子学习 HT 图形组件 – 3D建模篇
http://www.hightopo.com/demo/pipeline/index.html <数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇>里提到 HT 很 ...
- 数百个 HTML5 例子学习 HT 图形组件 – 3D 建模篇
http://www.hightopo.com/demo/pipeline/index.html <数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇>里提到 HT 很 ...
- 分享数百个 HT 工业互联网 2D 3D 可视化应用案例
过去的 2018 年,我们认为是国内工业互联网可视化的元年,图扑软件作为在工业可视化领域的重度参与者,一线见证了众多 HTML5/Web 化.2D/3D 化的项目在工业界应用落地,我们觉得有必要在此分 ...
- 近万字案例:Rancher + VMware PKS实现全球数百站点K8S集群管理
Sovereign Systems是一家成立于2007年的技术咨询公司,帮助客户将传统数据中心技术和应用程序转换为更高效的.基于云的技术平台,以更好地应对业务挑战.曾连续3年提名CRN,并且在2012 ...
- 数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇
HT 是啥:Everything you need to create cutting-edge 2D and 3D visualization. 这口号是当年心目中的产品方向,接着就朝这个方向慢慢打 ...
- 微软正式发布VS2015和.Net为开发者提供数百个新功能
今天,我很高兴地向大家宣布:Visual Studio 2015 和 .Net 4.6 的正式版本现已提供下载! 自去年十一月我们提出了微软开发技术的愿景:让所有开发者,无论他在什么平台,开发哪种应用 ...
- 分享数百个 HT 工业互联网 2D 3D 可视化应用案例之 2019 篇
继<分享数百个 HT 工业互联网 2D 3D 可视化应用案例>2018 篇,图扑软件定义 2018 为国内工业互联网可视化的元年后,2019 年里我们与各行业客户进行了更深度合作,拓展了H ...
- 数百个 HT 工业互联网 2D 3D 可视化应用案例分享 - 2019 篇
继<分享数百个 HT 工业互联网 2D 3D 可视化应用案例>2018 篇,图扑软件定义 2018 为国内工业互联网可视化的元年后,2019 年里我们与各行业客户进行了更深度合作,拓展了H ...
随机推荐
- java进阶(9)--数组
一.基本概念: 1.数字为引用数据类型 2.数组实际上是一个容器,可以同时容纳多个元素 3.数组可存储基本数据类型,也可以存储引用数据类型的数据 4.数组一旦创建.长度不可变.且数组中元素类型必须统一 ...
- redis 持久化机制及配置
本文为博主原创,未经允许不得转载: 目录: 1. RDB 2. AOF(append-only file) 3. RDB 和 AOF 特性比对 4. 混合持久化 redis 数据持久化共有两种方式:一 ...
- Pycharm配置git
原文链接:https://www.jianshu.com/p/ae92970d2062 1.下载Gitee插件 同样在设置页面,选中 Plugins,并搜索 Gitee安装. 安装后,重启一下Pych ...
- DEV-C++调试报错
1.报错信息如下: 2.原因 SIGSEGV是是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号. 意思是程序接受一个无效的指针地址,Segmentation fault即是提示我们去 ...
- 【SHELL】查找文件并删除
find . -iname file-name |xargs -I % rm -rf %
- [SpringMVC] - 解决 RequestMappingHandlerAdapter 报红的错误
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdap ...
- SpringBoot02:运行原理初探
@EnableAutoConfiguration @EnableAutoConfiguration:开启自动配置功能 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 @Ena ...
- Mygin实现分组路由Group
本篇是Mygin第五篇 目的 实现路由分组 为什么要分组 分组控制(Group Control)是 Web 框架应该提供的基础功能之一,对同一模块功能的开发,应该有相同的前缀.或者对一部分第三方接口, ...
- [转帖][MySQL 8.2.0] 从参数变化解读 MySQL 8.2.0 发版说明
https://www.mryunwei.com/482476.html 日前,MySQL 8.2.0 创新版本已正式上线,并提供安装包下载,但 docker 镜像尚未更新. 在 MySQL 8.1. ...
- [转帖]是什么让 Redis“气急败坏”回击:13 年来,总有人想替 Redis 换套新架构
https://www.infoq.cn/article/AlF5NIhHdskayl0MTyQG 回击就代表输了?! 今年年中,一位前谷歌.前亚马逊的工程师推出了他创作的开源内存数据缓存系统 Dra ...