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 ...
随机推荐
- Spring Boot 中使用Caffeine缓存的简单例子
Caffeine 缓存是 Java 的高性能缓存库.本文简单记录下 Caffeine 缓存的用法. 依赖配置 <dependencies> <dependency> <g ...
- AHB 局限性
AHB's problem SoC bus 架构 AXI is used more and more 频率200M使用AHB,频率再升高就使用AXI AHB的问题 AHB协议本身限制要求较高,比如co ...
- JMS 服务器健康检查
JMS所有服务器程序,包括Gateway.GatewayReferee.Proxy.TokenServer.以及编写的微服务器,都支持使用第三方工具进行健康检查. 使用telnet 进行健康检查 向任 ...
- Git-基本命令-init-add-commit-status
- 【转】国产飞腾D2000:基于A72?
https://zhuanlan.zhihu.com/p/612054128 China's Phytium D2000: Building on A72? 国产飞腾D2000:基于A72? PS:麒 ...
- [转帖]TLS/SSL (Schannel SSP) 中的密码套件
https://learn.microsoft.com/zh-cn/windows/win32/secauthn/cipher-suites-in-schannel 密码套件是一组加密算法. TLS/ ...
- [转帖]设置CMD默认代码页为65001或936
https://www.cnblogs.com/songzhenhua/p/9312769.html 之前不知道怎么改的,CMD的代码页被默认设置成了65001 但我右击CMD标题,选择'默认值' ...
- [转帖]Redhat 8 磁盘调度策略变化:NOOP改为NONE
说明 在 redhat 4/5/6/7版本中的NOOP调度策略,从8开始修改为NONE,官方解释: none Implements a first-in first-out (FIFO) schedu ...
- [转帖]金仓数据库KingbaseES误删除系统超级用户(superuser)权限的恢复方式
https://blog.csdn.net/arthemis_14/article/details/129879269 在使用KingbaseES数据库的时候,系统默认存在一个跟系统初始化用户同名的S ...
- Grafana监控OracleDB的完整过程
Grafana监控OracleDB的完整过程 背景 两年前曾经写过一个进行Oracle 监控的简单blog 但是周天晚上尝试进行处理时发现很不完整了. 很多数据获取不到. 晚上又熬夜了好久进行处理. ...