那些年,我们一起做过的性能优化

简介: 性能优化是一个体系化、整体性的事情,印刻在项目开发环节的各个细节中,也是体现技术深度的大的战场。文章以Quick BI的复杂系统为背景,详细介绍性能优化的思路和手段,以及体系化的思考。

一直以来,性能都是技术层面不可避开的话题,尤其在中大型复杂项目中。犹如汽车整车性能,追求极速的同时,还要保障舒适性和实用性,而在汽车制造的每个环节、零件整合情况、发动机调校等等,都会最终影响用户体感以及商业达成,如下图性能对收益的影响。

 

性能优化是一个体系化、整体性的事情,印刻在项目开发环节的各个细节中,也是体现技术深度的大的战场。下面我将以Quick BI的复杂系统为背景,深扒整个性能优化的思路和手段,以及体系化的思考。

如何定位性能问题?

 

通常来讲,我们对动画的帧率是比较敏感的(16ms内),但如果出现性能问题,我们的实际体感可能就一个字:“慢”,但这并不能为我们解决问题提供任何帮助,由此我们需要剖析这个字背后的整条链路。

 

上图是浏览器通用的处理流程,结合我们的场景,我这里抽象成以下几个步骤:

 

可以看出,主要的耗时阶段分为两个:

阶段一:资源包下载(Download Code)

阶段二:执行 & 取数(Script Execution & Fetch Data)

如何深入这两个阶段,我们一般会用以下几个主要的工具来分析:

Network

首先我们要使用的一个工具是Chrome的Network,它能帮助我们初步定位瓶颈所在的环节:

 

如图示例,在Network中可以一目了然看到整个页面的:加载时间(Finish)、加载资源大小、请求数量、每个请求耗时及耗时点、资源优先级等等。上面示例可以很明显看出:整个页面加载的资源很大,接近了30MB。

Coverage(代码覆盖率)

对于复杂的前端工程,其工程构建的产物一般会存在冗余甚至未被使用的情况,这些无效加载的代码可以通过Coverage工具来实时分析:

 

如上图示例可以看到:整个页面28.3MB,其中19.5MB都未被使用(执行),其中engine-style.css文件的使用率只有不到0.7%

资源大图

刚才我们已经知道前端资源的利用率非常低,那么具体是哪些无效代码被引入进来了?这时候我们要借助webpack-bundle-analyzer来分析整个的构建产物(产物stats可以通过webpack --profile --json=stats.json输出):

 

如上例,结合我们当前业务可以看到构建产物的问题:

第一,初始包过大(common.js)

第二,存在多个重复包(momentjs等)

第三,依赖的第三方包体积过大

模块依赖关系

有了资源构建大图,我们也大概知道了可优化的点,但在一个系统中,成百上千的模块一般都是通过互相引用的方式组织在一起,打包工具再通过依赖关系将其构建在一起(比如打成common.js单个文件),想要直接移除掉某个模块代码或依赖可能并非易事,由此我们可能需要一定程度抽丝剥茧,借助工具理清系统中模块的依赖关系,再通过调整依赖或加载方式来作优化:

 

上图我们使用到的是webpack官方的analyse工具(其他工具还有:webpack-xray,Madge),只需要将资源大图stats.json上传即可得到整个依赖关系大图

Performance

前面讲到的都是和资源加载相关的工具,那么在分析 “执行 & 取数” 环节我们使用什么,Chrome提供了非常强大的工具:Performance:

 

如上图示例,我们可以至少发现几个点:主流程串化、长任务、高频任务。

如何优化性能?

结合刚才提到的分析工具,刚才提到的 “资源包下载”、“执行 & 取数” 两个大的阶段我们基本上已经覆盖到,其根本问题和解法也在不断的分析中逐步有了思路,这里我将结合我们这里的场景,给出一些不错的优化思路和效果

大包按需加载

要知道,前端工程构建打包(如webpack)一般是从entry出发,去寻找整棵依赖树(直接依赖),从而根据这棵树产出多个js和css文件bundle或trunk,而一个模块一旦出现在依赖树中,那么当页面加载entry的时候,同时也会加载该模块。

所以我们的思路是打破这种直接依赖,针对末端的模块改用异步依赖方式,如下:

 

将同步的import { Marker } from '@antv/l7'改为异步,这样在构建时,被依赖的Marker会形成一个chunk,仅在此段代码执行时(按需),该thunk才被加载,从而减少了首屏包的体积。

然而上面方案会存在一个问题,构建会将整个@antv/l7作为一个chunk,而非Marker部分代码,导致该chunk的TreeShaking失效,体积很大。我们可以使用构建分片方式解决:

 

如上,先创建Marker的分片文件,使之具备TreeShaking的能力,再在此基础上作异步引入。

下方是我们优化后的流程对比结果:

 

这一步,我们通过按需拆包,异步加载,节省了资源下载时间和部分执行时间

资源预加载

其实我们在分析阶段已经发现一个“主流程串化”的问题,js的执行是单线程,但浏览器实际上是多线程运行的,这里面就包括异步请求(fetch等),所以我们进一步的思路是把取数(Fetch Data)与资源下载通过多线程并行。

按照当前现状,接口取数的逻辑一般是耦合在业务逻辑或数据处理逻辑中的,所以解耦(与UI、业务模块等解耦)的步骤必不可少,将纯粹的fetch请求(及少量处理逻辑)剥离出来,放到优先级更高的阶段来发起请求。那么放到什么地方呢?我们知道,浏览器对资源的处理是有优先级的,正常按如下顺序:

  1. HTML/CSS/FONT
  2. Preload/SCRIPT/XHR
  3. Image/Audio/Video
  4. Prefetch

要做到资源拉取 和 发起取数并行,就有必要把取数提前到第1优先级(HTML解析完毕后立即执行,而非等待SCRIPT标签资源加载执行过程中发起请求),我们的流程会变成如下:

 

需要特别注意一点:由于JS的执行是串行,发起取数的那段逻辑必须要先于主流程逻辑执行,并且不能放到nextTick(如使用setTimeout(() => doFetch())),否则主流程会一直占用CPU时间使得请求无法发出

主动任务调度

浏览器对资源也有优先级策略,但它并不知道业务层面的我们,到底想要哪些资源先加载/执行,哪些资源后加载/执行,所以我们跳出来看,若把整个业务层面的资源加载+执行/取数流程拆成一个一个小的任务,这些任务全权由我们自己来控制其:打包粒度、加载时机、执行时机,是不是意味着能最大化利用CPU时间和网络资源了?

答案是肯定的,不过一般对于简单的项目,浏览器本身的调度优先级策略已经足够满足需要,但如果针对大型复杂项目,要做的相对极致的优化,就有必要引入“自定义任务调度”方案了。

以Quick BI为例,我们的前期目标是:让首屏主要内容展现更加快速。那么从资源加载、代码执行、取数层面是应该根据我们业务优先级作CPU/网络分配的,比如:我希望“卡片的下拉菜单”,在首屏主要内容展示完毕后或CPU空闲时,才开始加载(即降低优先级,更甚至在用户鼠标移入卡片中时,又希望它提高优先级立即开始加载并展示)。如下:

 

这里我们封装了一个任务调度器,其目的是可以声明一段逻辑,在其某个依赖(Promise)完成后开始执行。我们的流程图变化如下:

 

黄色区块代表 作优先级降级处理的部分模块,其帮助减少了整个首屏时间

TreeShaking

上面讲方法大多从优先级出发,其实在前端工程化日益复杂的时代(中大型项目已超几十万行代码),诞生了一个较为智能的优化方案用于减少包大小,其思想很简单:工具化分析依赖关系,将没有被引用到的代码从最终产物中剔除掉。

听起来很酷,实际用起来也非常不错,但这里想讲一些很多其官网也不会提到的点 --- TreeShaking经常失效的情况:

副作用

副作用(Side Effects)通常表达的是对全局(如window对象等)或环境会产生影响的代码。

 

如图示例,b代码看似未被使用,但其文件中存在console.log(b(1))这样的代码,webpack等打包工具不敢轻易移除它,所以它会被照常打入。

解决方法

在package.json 或 webpack配置中明确指定哪些代码具备副作用(例如sideEffects: [“**/*.css”]),无副作用的代码将被移除

IIFE类代码

IIFE即会被立即执行的函数表达式(Immediately invoked function expression)

 

如图,这类型的代码,会导致TreeShaking失效

解决方法

三个原则:

  • [避免]立即执行的函数调用
  • [避免]立即执行的new操作
  • [避免]立即影响全局的代码

懒加载

我们在“按需加载”处提到过异步import来做拆包会导致TreeShaking失效,这里再进一步说明一下另外一个case:

 

如图,由于index.ts同步import了bar.ts中的sharedStr,然后在某个地方,又同时异步import('./bar'),这种情况下,会同时导致两个问题:

  1. TreeShaking失效(unusedStr会被打入)
  2. 异步懒加载失效(bar.ts会和index.ts打入到一起)

当代码量达到一定量级,N个人协同开发就很容易出现这个问题

解决方法

  • [避免]同步和异步import同个文件

按需策略(Lazy)

其实前面有讲到一些按需加载的方案,这里我们适当延伸一下:既然资源包的加载可以做到按需,是否某个组件的渲染可以按需?某个对象实例的使用可以按需?某个数据缓存的生成也可以按需?

懒组件(LazyComponent)

 

如图,PieArc.private.ts对应一个复杂的React组件,PieArc通过makeLazyComponent封装成默认懒加载的组件,只有在代码执行到此处时,组件才会加载并执行。甚至,还可以通过第二个参数(deps)申明依赖,待依赖(promise)完毕时,才加载和执行。

懒缓存(LazyCache)

懒缓存用于这种场景:需要在任何地方使用到数据流(或其他可订阅数据)中的某个数据经过转换后的结果,且仅在使用的那一刻才进行转换

 

懒对象(LazyObject)

懒对象意即该对象只有在被使用的时候(属性/方法被访问、修改、删除等等),才会被实例化

 如图,globalRecorder被引入时,其并未实例化,仅当调用globalRecorder.record()时进行实例化

数据流:节流渲染

中大型项目中为了方便状态管理,通常会使用到数据流的方案,如下流程:

 

store中存储的数据通常偏原子化,粒度非常小,比如state中有:a、b、c ...等N个原子属性,某个组件依赖这N个属性来作UI渲染,假设N个属性会在不同的ACTION下被改变,且这些改变均在16ms内发生,那么若N=20,则16ms内(1帧)会有20次View更新:

 

这显然会引发非常大的性能问题,由此,我们需要对短时间的ACTION量作一个缓冲节流,待20次ACTION状态改变完毕后,仅进行1次View更新,如下:

 

此方案在Quick BI以redux中间件的形式发挥作用,在复杂+频繁数据更新场景起到了不错的效果

思考

“君子以思患而豫防之”,当我们回过头去看看,出现的这些性能问题,在架构设计、编码阶段是可以避免掉80%以上的,20%的则可以“空间<=>时间置换策略”等方式去平衡。所以,最佳的性能优化方案,是在于我们对每一段代码质量的执着:是否考虑到了这样的模块依赖关系,可能带来的构建产物体积问题?是否考虑到了这段逻辑可能的执行频次?是否考虑到了随着数据增长,空间或CPU占用的可控性?等等。性能优化没有银弹,作为技术人,需要内修于心(熟知底层原理),把对性能的执念植入本能思考当中,方为银弹。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512965.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中继承父类实现父类方法的快捷键_关于封装、继承

在初期&#xff0c;很多人对于java中一些定义的认识比较模糊&#xff0c;今天我就来详细讲一讲我所认识的封装和继承。1、封装1.1&#xff09;概念&#xff1a;将类的某些信息隐藏在内部&#xff0c;不允许外部程序直接访问&#xff0c;而是通过该类提供的方法来实现对隐藏信息…

KubeVela 上手(1)|让云端应用交付更加丝滑

简介&#xff1a; KubeVela 是阿里云和微软共同发起的 OAM&#xff08;Open Application Model&#xff09;标准的技术实现&#xff0c;旨在打造统一、标准、跨环境的云端应用交付&#xff0c;省时省力&#xff0c;轻松简单 作者&#xff5c;KubeVela 社区 本文适合所有软件工…

华为云云原生首次在太空验证,提升“天算星座“卫星计算精度

12月10日&#xff0c;搭载“天算星座”计算平台的试验卫星在轨稳定运行&#xff0c;华为云“云边一体”方案首次在太空验证。 图&#xff1a;天算星座计划 “天算星座”计划&#xff0c;由北京邮电大学深圳研究生院与天仪研究院共同发起&#xff0c;以服务国家重大战略需求和…

火柴人_火柴人战争遗产修改无限金币钻石

火柴人战争遗产修改无限金币钻石V1.11.160/中文/80M商店内使用钻石购买商品不减反增【一、游戏简介】《火柴人战争遗产修改版 Stick War: Legacy》一直最受玩家欢迎、评分最高的一款网游现在推出移动版.玩 Stick War&#xff0c;体验这款最受欢迎、最有趣、最具挑战性且容易入迷…

一种通用整形数组压缩方法

简介&#xff1a; 我们在开发中后台应用或者中间件的时候&#xff0c;会存储一些数据在内存中以加快访问速度。随着数据量的增加&#xff0c;除了可以放置于堆外&#xff0c;还可以通过实时压缩来缓解。今天就给大家介绍一种压缩整形数组的方式。 作者 | 玄胤 来源 | 阿里技术公…

gitlab git clone 卡住_gitlab从入门到绝望

啥年月了还用svn&#xff0c;日了狗一样难受。开搞&#xff01;docker是最好的容器&#xff0c;直接docker装gitlab。学新玩意不去官网不是人&#xff1a;https://docs.gitlab.com/omnibus/docker/#expose-gitlab-on-different-portsdocker pull gitlab/gitlab-cesudo docker r…

FBEC2021暨第六届金陀螺奖颁奖典礼盛大开幕

2021年12月10日&#xff0c;由广东省游戏产业协会、广东省虚拟现实产业技术创新联盟、深圳市科学技术协会、深圳市互联网文化市场协会指导&#xff0c;陀螺科技主办&#xff0c;深圳市科技开发交流中心、恒悦创客魔方协办&#xff0c;行业头部媒体游戏陀螺、VR陀螺、陀螺电竞、…

RDS PostgreSQL一键大版本升级技术解密

简介&#xff1a; 内容简要&#xff1a; 一、PostgreSQL行业位置 二、PostgreSQL版本升级背景 三、PostgreSQL版本升级解密 四、PostgreSQL版本升级成果 一、PostgreSQL行业位置 &#xff08;一&#xff09;行业位置 在讨论PostgreSQL&#xff08;下面简称为PG&#xff09;在…

环境变量_Jenkins流水线环境变量权威指南

你是否遇到过因环境变量问题导致调试流水线很长时间&#xff1f;这篇文章一定能解决你的问题。本文章翻译自博客。欢迎来到“Jenkins CookBook”系列的第一篇博客文章。今天&#xff0c;我们专注于有效地使用Jenkins Pipeline环境变量。您将学习如何定义env变量&#xff0c;如何…

浅谈RSocket与响应式编程

简介&#xff1a; RSocket是高效一个二进制的网络通讯协议&#xff0c;能够满足很多场景下使用。另外&#xff0c;RSocket也是一个激进的响应式捍卫者&#xff0c;激进到连API都跟响应式无缝集成。本文我们将和大家分享RSocket与响应式编程。 作者 | 素渡 来源 | 阿里技术公众号…

Go语言重新开始,Go Modules 的前世今生与基本使用

随着Go语言发展与场景变化&#xff0c; GOPATH 引起的编译依赖、内部自签发证书、安全审计等问题相继出现&#xff0c;随着官方推出的Go Modules逐渐完善&#xff0c;用户有了新的选择。本文将会带领大家从0开始&#xff0c;认识并使用Go Modules。 2020 年腾讯内部的一份开发者…

MaxCompute中如何通过logview诊断慢作业

简介&#xff1a; MaxCompute致力于批量结构化数据的存储和计算&#xff0c;提供海量数据仓库的解决方案及分析建模服务&#xff0c;在MaxCompute执行sql任务的时候有时候作业会很慢&#xff0c;本文通过查看logview排查具体任务慢的原因 在这里把任务跑的慢的问题划分为以下几…

excel超级工具箱_这6个Excel高效办公插件,你都用过吗?

1.易用宝。ExcelHome出品&#xff0c;永久免费&#xff0c;让繁琐或难以实现的操作变得简单可行&#xff0c;甚至能够一键完成&#xff0c;所有这些功能都将极大地提升 Excel 的便捷以及可用性&#xff01;地址&#xff1a;http://yyb.excelhome.net2.方方格子工具箱。大部分功…

Yurt-Tunnel 详解|如何解决 K8s 在云边协同下的运维监控挑战

简介&#xff1a; 伴随着 5G、IoT 等技术的快速发展&#xff0c;边缘计算被越来越广泛地应用于电信、媒体、运输、物流、农业、零售等行业和场景中&#xff0c;成为解决这些领域数据传输效率的关键方式。与此同时&#xff0c;边缘计算形态、规模、复杂度的日益增长&#xff0c;…

RTC风向标:11月最值得关注的26个热点

近年来&#xff0c;实时音视频快速发展&#xff0c;WebRTC作为实时音视频的标准也快速发展&#xff0c;从直播到通讯&#xff0c;其应用场景也在不断丰富。如果您关注实时音视频方向的技术产品应用与创新&#xff0c;本系列文章就将会为您分享音视频方向的技术产品动态&#xf…

克隆需要验证_[实验技巧]CRISPR实验中如何验证编辑?

在CRISPR/Cas9基因组编辑实验中&#xff0c;如果你已经构建好了gRNA表达载体&#xff0c;并利用Cas9将它引入了目标细胞&#xff0c;那么恭喜你&#xff01;成功就在眼前&#xff0c;指日可待。下一步&#xff0c;你还要验证一下&#xff0c;看看细胞的编辑是否如你所愿。在此&…

基于边缘云的机器流量管理技术实战

简介&#xff1a; CDN是通过在全球范围内分布式地部署边缘服务器将各类互联网内容缓存到靠近用户的边缘服务器上&#xff0c;从而降低用户访问时延并大幅减少穿越互联网核心网的流量。互联网业务使用CDN已经成为一种必然的选择。 企业边缘应用面临的挑战 CDN是通过在全球范围…

python中的repr是什么意思_python中str和repr有什么区别

python中str和repr有什么区别&#xff1f;下面给大家详细介绍&#xff1a; 1、内建函数str()和repr() 或反引号操作符&#xff08;&#xff09;可以方便地以字符串的方式获取对象的内容、类型、数值属性等信息。 2、str()函数得到的字符串可读性好&#xff08;故被print调用&am…

Go语言入门分享

简介&#xff1a; Go语言出自Ken Thompson、Rob Pike和Robert Griesemer之手&#xff0c;起源于2007年&#xff0c;并在2009年正式对外发布。Go的主要目标是“兼具Python等动态语言的开发速度和C/C等编译型语言的性能与安全性”&#xff0c;旨在不损失应用程序性能的情况下降低…

瑞欧威尔联合创始人兼CEO 李波博士:“工业元宇宙”是为了更好赋能实体经济

2021年12月10日&#xff0c;由广东省游戏产业协会、广东省虚拟现实产业技术创新联盟、深圳市科学技术协会、深圳市互联网文化市场协会指导&#xff0c;陀螺科技主办&#xff0c;深圳市科技开发交流中心、恒悦创客魔方协办&#xff0c;行业头部媒体游戏陀螺、VR陀螺、陀螺电竞、…