前端优化系列 - JS混淆引入性能天坑
前言
现在用户手机性能,浏览器性能,网络性能,越来越好,后端逻辑逐渐向前端转移,前端渲染变得越来越普遍。前端渲染主要依赖JS去完成核心逻辑,JS正变得越来越重要。而JS文件是以源码的形式传输,可以在Chrome Devtools上轻易地被修改和调试。我们一般不希望核心业务逻辑轻易的被别人了解,往往会通过代码混淆的方式去进行保护。
那么,代码混淆对JS性能是否有影响呢?我们下面讨论一个真实的案例,看看混淆如何让JS性能变差100倍,并详细介绍如何去跟进和处理类似问题。
混淆引入性能问题
通常JS混淆有两种方式,一种是正则替换,强度比较弱,很容易被破解;另外一种是修改抽象语法树,比较难破解。
一些比较重要的JS文件,一般会使用修改抽象语法树的方式去进行混淆保护。相关的原理请参考知乎上的文章:前端如何给 JavaScript 加密
一般来说,JS混淆会引入多余代码,修改原来的抽象语法树,可能会引入性能问题,但性能影响一般非常小。
但是,也有异常的情况,我们在一个业务上发现它的isdsp_securitydata_send.js执行非常耗时,竟然达到惊人的1.6秒。Trace信息如下,
而使用它未混淆的源码去执行时,发现在15毫秒就执行完了。这是一个非常明显的混淆引入性能问题的案例。
分析性能问题
大部分问题,在找到根本原因之后,我们都会觉得非常简单,也很容易解决。而分析问题原因的过程和方法则更加重要,我们下面分享一些通用的分析问题的方法。
(1)确认性能问题
一般来说,确认一个JS执行是否存在性能问题,使用Chrome Trace还是比较方便的。我们下面先说说怎么看Trace信息。
上图中,
v8.run 对应内核的V8ScriptRunner::runCompiledScript, 代表blink端的JS的执行时间,即JS执行的实际耗时。
V8.Execute 代表v8内部的JS执行时间,与v8.run代表的意义一样,耗时也相近。
颜色与V8.ParseLazy一样的部分,代表JS编译耗时,从上图可以看到,编译耗时占了绝大部分。
注:上图仅仅为了展示Trace中V8相关的含义,不是我们要讨论的JS耗时问题。
我们再来看看存在性能问题的Trace信息,
从上图可以看到,v8.run下面几乎没有蓝色的片段,即几乎没有编译耗时,基本上都是JS代码执行的耗时。
这样我们可以判断,isdsp_securitydata_send.js 执行的耗时达到了惊人的1.6秒,而这个JS的逻辑非常简单,它很有可能是存在严重性能问题的。
注:上图是isdsp_securitydata_send.js在真实环境执行消耗的时间。
(2)分析问题原因
在上面我们已经定位到isdsp_securitydata_send.js的执行耗时存在较大问题,那么可以怎么去定位问题的准确原因呢?
我们先将问题简化,把这个JS抽取出来单独去执行,比如,使用下面示例代码:
<html> <body> <script type= "text/javascript" src= "https://gw.alipayobjects.com/os/secjs/a0482061-5cc3-4684-a115-ed8c61fca4b3/isdsp_securitydata_send.js" ></script> </body> </html> |
然后抓取该示例代码的Trace信息,
从上面Trace可以看到,里面一些JS函数的执行非常耗时,每个耗时都有几百毫秒。
但这个外联的JS是无法定位到代码行的,我们可以将外联JS文件的内容直接拷贝到上述<script>标签里面去执行,看看具体的代码行在哪里?
从上图可以发现,耗时的代码在2117行,直接点击可以定位到具体的代码行,
从上图可以看到,下面函数执行非常耗时,耗时800多毫秒。
function a(r) { var n = Mo; var a = sn; for ( var o = S; o < r[L[No + J[Lo](U)](U) + P[Qo + Z[Lo](U)](U)]; o++) { var t = ((r[yr[No + J[Lo](U)](U) + mv + xv](o) - _) * cr + X - a) % V + _; n += String[Oa[Wo + D[Lo](U)](U) + ad + fr[Qo + Z[Lo](U)](U) + kt](t); a = t } return n } |
上述函数为什么会非常耗时呢?这里就是JS引擎专家发挥的地方了! 我们通过分析JS引擎的执行发现,String[Oa[Wo + D[Lo](U)](U) + ad + fr[Qo + Z[Lo](U)](U) + kt](t) 这一句代码,其实是 s += String.fromCharCode(p) 混淆之后的结果。
这种混淆会带来什么问题呢?V8和JSC引擎的字符串拼接查找性能都非常弱,比如,String["toS" + "tring"](),number to string,都是V8和JSC引擎的超级弱点。
JS字符串拼接的性能为什么会很差呢?
在JavaScript中,字符串是不可变的(immutable),只能被另外一个字符串替换。
var combined = ""; for (var i = 0; i < 1000000; i++) { combined = combined + "hello "; }
上述示例代码中,combined + "hello " 不会直接修改combined变量,而会新建一个临时对象存储计算结果,然后再使用该临时对象替换combined变量。所以上述for循环中会产生海量的临时变量,JS引擎GC需要大量工作来清理这些临时变量,从而会影响性能。
注:上述解析来自Why is + so bad for concatenation?
我们再进一步去验证去掉字符串混淆的代码效果,(注:出于信息安全考虑,不提供去混淆的JS示例)
我们看看改动之后的JS执行的Trace信息,
从上图可以看到,isdsp_securitydata_send.js在几毫秒就执行完了。
我们再在真实的业务页面上验证优化后的效果,
执行耗时直接从1.6秒,优化为15毫秒,优化幅度大于100倍!
解决性能问题
从上面的分析可以看到,JS混淆引入了大量的字符串拼接,从而导致性能大幅下降。
那么,解决问题的方案也就很显然了,那就是去掉这些字符串拼接,即降低混淆的强度,把字符串混淆部分去掉。
去掉字符串混淆部分之后,isdsp_securitydata_send.js的执行耗时变为15毫秒,完美的实现了优化。
结束语
现在前端渲染非常流行,页面大部分逻辑由JS控制。从我们长期进行页面性能优化的经验来看,页面性能优化的20-40%与浏览器内核相关,而60-80%与前端JS相关,即前端JS是性能优化的重中之重。
那么,前端JS优化有那些比较好的实践呢?内核直接参与分析前端JS,成本非常大,并非长久之计,内核更应该做的是赋能前端。
在赋能前端方面,内核可以做那些事情呢?
(1)将一些通用的前端分析方法整理成文档,供前端参考。
(2)将一些人工分析总结的经验,固化到自动化的工具,比如,WDPS Lighthouse。
(3)提供一些更有效的分析工具。比如,在Trace中更清晰的展现JS引擎的运行逻辑。
(4)与前端更多交流合作,建立互信,深入合作研究疑难问题和普遍问题。
参考文档
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
redis事物详解
redis事物详解 Redis 通过MULTI、DISCARD、EXEC和WATCH四个命令来实现事务功能, 本章首先讨论使用MULTI、DISCARD和EXEC三个命令实现的一般事务, 然后再来讨论带有WATCH的事务的实现。 因为事务的安全性也非常重要, 所以本章最后通过常见的 ACID 性质对 Redis 事务的安全性进行了说明。 事务 事务提供了一种“将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令。 以下是一个事务的例子, 它先以MULTI开始一个事务, 然后将多个命令入队到事务中, 最后由EXEC命令触发事务, 一并执行事务中的所有命令: redis> MULTI OK redis> SET book-name "Mastering C++ in 21 days" QUEUED redis> GET book-name QUEUED redis> SADD tag "C++" "Programming" "Mastering Serie...
- 下一篇
Go HttpServer 最佳实践
这是 Cloudflare 的 Filippo Valsorda 2006年发表在Gopher Academy的一篇文章, 虽然过去两年了,但是依然很有意义。 先前 crypto/tls 太慢而net/http也很年轻, 所以对于Go web server来说, 通常我们明智的做法把它放在反向代理的后面, 如nginx等,现在不需要了。 在Cloudflare我们最近试验了直接暴漏纯Go的服务作为主机。 Go 1.8的net/http 和 crypto/tls 提供了稳定的、高性能并且灵活的功能。 然后,需要做一些调优的工作,本文我们将展示怎么去调优和使web服务器更稳定。 crypto/tls 2016年了,你不会再运行一个不加密的HTTP Server,所以你需要crypto/tls。好消息使这个库已经非常快了(我们的测试),目前他的安
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程