快捷搜索:

澳门金莎娱乐抽象语法树,启动性能瓶颈分析与解决方案

2019-11-19 22:44栏目:新闻资讯
TAG:

JavaScript 启动性能瓶颈分析与解决方案

2017/02/17 · JavaScript · 性能

原文出处: Addy Osmani   译文出处:王下邀月熊_Chevalier   

澳门金莎娱乐 1

在 Web 开发中,随着需求的增加与代码库的扩张,我们最终发布的 Web 页面也逐渐膨胀。不过这种膨胀远不止意味着占据更多的传输带宽,其还意味着用户浏览网页时可能更差劲的性能体验。浏览器在下载完某个页面依赖的脚本之后,其还需要经过语法分析、解释与运行这些步骤。而本文则会深入分析浏览器对于 JavaScript 的这些处理流程,挖掘出那些影响你应用启动时间的罪魁祸首,并且根据我个人的经验提出相对应的解决方案。回顾过去,我们还没有专门地考虑过如何去优化 JavaScript 解析/编译这些步骤;我们预想中的是解析器在发现<script>标签后会瞬时完成解析操作,不过这很明显是痴人说梦。下图是对于 V8 引擎工作原理的概述:
澳门金莎娱乐 2下面我们深入其中的关键步骤进行分析。

在 Web 开发中,随着需求的增加与代码库的扩张,我们最终发布的 Web 页面也逐渐膨胀。不过这种膨胀远不止意味着占据更多的传输带宽,其还意味着用户浏览网页时可能更差劲的性能体验。浏览器在下载完某个页面依赖的脚本之后,还需要经过语法分析、解释与运行这些步骤。而本文则会深入分析浏览器对于 JavaScript 的这些处理流程,挖掘出那些影响你应用启动时间的罪魁祸首,并且根据我个人的经验提出相对应的解决方案。回顾过去,我们还没有专门地考虑过如何去优化 JavaScript 解析/编译这些步骤;我们预想中的是解析器在发现<script>标签后会瞬时完成解析操作,不过这很明显是痴人说梦。下图是对于 V8 引擎工作原理的概述:

摘要:JS的”编译原理”。

到底是什么拖慢了我们应用的启动时间?

在启动阶段,语法分析,编译与脚本执行占据了 JavaScript 引擎运行的绝大部分时间。换言之,这些过程造成的延迟会真实地反应到用户可交互时延上;譬如用户已经看到了某个按钮,但是要好几秒之后才能真正地去点击操作,这一点会大大影响用户体验。
澳门金莎娱乐 3上图是我们使用 Chrome Canary 内置的 V8 RunTime Call Stats 对于某个网站的分析结果;需要注意的是桌面浏览器中语法解析与编译占用的时间还是蛮长的,而在移动端中占用的时间则更长。实际上,对于 Facebook, Wikipedia, Reddit 这些大型网站中语法解析与编译所占的时间也不容忽视:
澳门金莎娱乐 4上图中的粉色区域表示花费在 V8 与 Blink’s C++ 中的时间,而橙色和黄色分别表示语法解析与编译的时间占比。Facebook 的 Sebastian Markbage 与 Google 的 Rob Wormald 也都在 Twitter 发文表示过 JavaScript 的语法解析时间过长已经成为了不可忽视的问题,后者还表示这也是 Angular 启动时主要的消耗之一。
澳门金莎娱乐 5

随着移动端浪潮的涌来,我们不得不面对一个残酷的事实:移动端对于相同包体的解析与编译过程要花费相当于桌面浏览器2~5倍的时间。当然,对于高配的 iPhone 或者 Pixel 这样的手机相较于 Moto G4 这样的中配手机表现会好很多;这一点提醒我们在测试的时候不能仅用身边那些高配的手机,而应该中高低配兼顾:
澳门金莎娱乐 6

上图是部分桌面浏览器与移动端浏览器对于 1MB 的 JavaScript 包体进行解析的时间对比,显而易见的可以发现不同配置的移动端手机之间的巨大差异。当我们应用包体已经非常巨大的时候,使用一些现代的打包技巧,譬如代码分割,TreeShaking,Service Workder 缓存等等会对启动时间有很大的影响。另一个角度来看,即使是小模块,你代码写的很糟或者使用了很糟的依赖库都会导致你的主线程花费大量的时间在编译或者冗余的函数调用中。我们必须要清醒地认识到全面评测以挖掘出真正性能瓶颈的重要性。

澳门金莎娱乐 7

  • 原文:JavaScript的工作原理:解析、抽象语法树+ 提升编译速度5个技巧

在建立那些严重依赖于JavaScript网站的时候,有时我们会为自己发送的内容付出一些隐形的成本。在本篇文章中,我会介绍一些可以帮助你提升网站在移动设备上加载和运行速度的实用规则。

JavaScript 语法解析与编译是否成为了大部分网站的瓶颈?

我曾不止一次听到有人说,我又不是 Facebook,你说的 JavaScript 语法解析与编译到
底会对其他网站造成什么样的影响呢?对于这个问题我也很好奇,于是我花费了两个月的时间对于超过 6000 个网站进行分析;这些网站囊括了 React,Angular,Ember,Vue 这些流行的框架或者库。大部分的测试是基于 WebPageTest 进行的,因此你可以很方便地重现这些测试结果。光纤接入的桌面浏览器大概需要 8 秒的时间才能允许用户交互,而 3G 环境下的 Moto G4 大概需要 16 秒 才能允许用户交互。
澳门金莎娱乐 8大部分应用在桌面浏览器中会耗费约 4 秒的时间进行 JavaScript 启动阶段(语法解析、编译、执行)
澳门金莎娱乐 9

而在移动端浏览器中,大概要花费额外 36% 的时间来进行语法解析:
澳门金莎娱乐 10

另外,统计显示并不是所有的网站都甩给用户一个庞大的 JS 包体,用户下载的经过 Gzip 压缩的平均包体大小是 410KB,这一点与 HTTPArchive 之前发布的 420KB 的数据基本一致。不过最差劲的网站则是直接甩了 10MB 的脚本给用户,简直可怕。

澳门金莎娱乐 11

通过上面的统计我们可以发现,包体体积固然重要,但是其并非唯一因素,语法解析与编译的耗时也不一定随着包体体积的增长而线性增长。总体而言小的 JavaScript 包体是会加载地更快(忽略浏览器、设备与网络连接的差异),但是同样 200KB 的大小,不同开发者的包体在语法解析、编译上的时间却是天差地别,不可同日而语。

image.png

Fundebug经授权转载,版权归原作者所有。

tl;dr:更少的代码 = 更少的解析/编译 (parse/compile)+ 更少的传送 + 更少的解压缩

现代 JavaScript 语法解析 & 编译性能评测

下面我们深入其中的关键步骤进行分析。

这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇。

网络

Chrome DevTools

打开 Timeline( Performance panel ) > Bottom-Up/Call Tree/Event Log 就会显示出当前网站在语法解析/编译上的时间占比。如果你希望得到更完整的信息,那么可以打开 V8 的 Runtime Call Stats。在 Canary 中,其位于 Timeline 的 Experims > V8 Runtime Call Stats 下。
澳门金莎娱乐 12

到底是什么拖慢了我们应用的启动时间?

在启动阶段,语法分析、编译与脚本执行占据了 JavaScript 引擎运行的绝大部分时间。换言之,这些过程造成的延迟会真实地反应到用户可交互时延上;譬如用户已经看到了某个按钮,但是要好几秒之后才能真正地去点击操作,这一点会大大影响用户体验。下图是我们使用 Chrome Canary 内置的 V8 RunTime Call Stats 对于某个网站的分析结果:

澳门金莎娱乐 13

image.png

需要注意的是桌面浏览器中语法解析与编译占用的时间还是蛮长的,而在移动端中占用的时间则更长。实际上,对于 Facebook, Wikipedia, Reddit 这些大型网站中语法解析与编译所占的时间也不容忽视,下图中的粉色区域表示花费在 V8 与 Blink's C++ 中的时间,而橙色和黄色分别表示语法解析与编译的时间占比:

澳门金莎娱乐 14

image.png

Facebook 的 Sebastian Markbage 与 Google 的 Rob Wormald 也都在 Twitter 发文表示过 JavaScript 的语法解析时间过长已经成为了不可忽视的问题,后者还表示这也是 Angular 启动时主要的消耗之一。

澳门金莎娱乐 15

image.png

随着移动端浪潮的涌来,我们不得不面对一个残酷的事实:移动端对于相同包体的解析与编译过程要花费相当于桌面浏览器2~5倍的时间。当然,对于高配的 iPhone 或者 Pixel 这样的手机相较于 Moto G4 这样的中配手机表现会好很多;这一点提醒我们在测试的时候不能仅用身边那些高配的手机,而应该中高低配兼顾:

澳门金莎娱乐 16

image.png

上图是部分桌面浏览器与移动端浏览器对于 1MB 的 JavaScript 包体进行解析的时间对比,显而易见的可以发现不同配置的移动端手机之间的巨大差异。当我们应用包体已经非常巨大的时候,使用一些现代的打包技巧,譬如代码分割,TreeShaking,Service Workder 缓存,等等会对启动时间有很大的影响。另一个角度来看,即使是小模块,你代码写的很糟或者使用了很糟的依赖库都会导致你的主线程花费大量的时间在编译或者冗余的函数调用中。我们必须要清醒地认识到全面评测以挖掘出真正性能瓶颈的重要性。

如果你错过了前面的章节,可以在这里找到它们:

大多数开发人员考虑JavaScript成本的时候,考虑的都是下载和执行成本。通过线路发送的JavaScript字节越多,所需时间就越长,用户连接就越慢。

Chrome Tracing

打开 about:tracing 页面,Chrome 提供的底层的追踪工具允许我们使用disabled-by-default-v8.runtime_stats来深度了解 V8 的时间消耗情况。V8 也提供了详细的指南来介绍如何使用这个功能。
澳门金莎娱乐 17

JavaScript 语法解析与编译是否成为了大部分网站的瓶颈?

我曾不止一次听到有人说,我又不是 Facebook,你说的 JavaScript 语法解析与编译到底会对其他网站造成什么样的影响呢?对于这个问题我也很好奇,于是我花费了两个月的时间对于超过 6000 个网站进行分析;这些网站囊括了 React,Angular,Ember,Vue 这些流行的框架或者库。大部分的测试是基于 WebPageTest 进行的,因此你可以很方便地重现这些测试结果。光纤接入的桌面浏览器大概需要 8 秒的时间才能允许用户交互,而 3G 环境下的 Moto G4 大概需要 16 秒 才能允许用户交互。

澳门金莎娱乐 18

image.png

大部分应用在桌面浏览器中会耗费约 4 秒的时间进行 JavaScript 启动阶段(语法解析、编译、执行):

澳门金莎娱乐 19

image.png

而在移动端浏览器中,大概要花费额外 36% 的时间来进行语法解析:

澳门金莎娱乐 20

image.png

另外,统计显示并不是所有的网站都甩给用户一个庞大的 JS 包体,用户下载的经过 Gzip 压缩的平均包体大小是 410KB,这一点与 HTTPArchive 之前发布的 420KB 的数据基本一致。不过最差劲的网站则是直接甩了 10MB 的脚本给用户,简直可怕。

澳门金莎娱乐 21

image.png

通过上面的统计我们可以发现,包体体积固然重要,但是其并非唯一因素,语法解析与编译的耗时也不一定随着包体体积的增长而线性增长。总体而言小的 JavaScript 包体是会加载地更快(忽略浏览器、设备与网络连接的差异),但是同样 200KB 的大小,不同开发者的包体在语法解析、编译上的时间却是天差地别,不可同日而语。

  • JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述!

  • JavaScript 是如何工作的:深入V8引擎&编写优化代码的5个技巧!

  • JavaScript 是如何工作的:内存管理+如何处理4个常见的内存泄漏 !

  • JavaScript 是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!

  • JavaScript 是如何工作的:深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!

  • JavaScript 是如何工作的:与 WebAssembly比较 及其使用场景 !

  • JavaScript 是如何工作的:Web Workers的构建块+ 5个使用他们的场景!

  • JavaScript 是如何工作的:Service Worker 的生命周期及使用场景!

  • JavaScript 是如何工作的:Web 推送通知的机制!

  • JavaScript是如何工作的:使用 MutationObserver 跟踪 DOM 的变化!

  • JavaScript是如何工作的:渲染引擎和优化其性能的技巧!

  • JavaScript是如何工作的:深入网络层 + 如何优化性能和安全!

  • JavaScript是如何工作的:CSS 和 JS 动画底层原理及如何优化它们的性能!-

澳门金莎娱乐 22

WebPageTest

澳门金莎娱乐 23WebPageTest 中 Processing Breakdown 页面在我们启用 Chrome > Capture Dev Tools Timeline 时会自动记录 V8 编译、EvaluateScript 以及 FunctionCall 的时间。我们同样可以通过指明disabled-by-default-v8.runtime_stats的方式来启用 Runtime Call Stats。
澳门金莎娱乐 24

更多使用说明参考我的gist点击预览。

现代 JavaScript 语法解析 & 编译性能评测

概述

我们都知道运行一大段 JavaScript 代码性能会变得很糟糕。这段代码不仅需要通过网络传输,而且还需要解析、编译成字节码,最后执行。在之前的文章中,我们讨论了 JS 引擎、运行时和调用堆栈等,以及主要由谷歌 Chrome 和 NodeJS 使用的V8引擎。它们在整个 JavaScript 执行过程中都发挥着至关重要的作用。这篇说的抽象语法树同样重要:在这我们将了解大多数 JavaScript 引擎如何将文本解析为对机器有意义的内容,转换之后发生的事情以及做为 Web 开发者如何利用这一知识。

即使是在发达国家,这也可能是一个问题,因为用户实际上用的有效网络连接类型可能并不是3G、4G或者Wifi。表面上你可能连的是咖啡店的Wifi,但实际上连到的是只有2G速度的蜂窝热点。

User Timing

我们还可以使用 Nolan Lawson 推荐的User Timing API来评估语法解析的时间。不过这种方式可能会受 V8 预解析过程的影响,我们可以借鉴 Nolan 在 optimize-js 评测中的方式,在脚本的尾部添加随机字符串来解决这个问题。我基于 Google Analytics 使用相似的方式来评估真实用户与设备访问网站时候的解析时间:
澳门金莎娱乐 25

Chrome DevTools

打开 Timeline( Performance panel ) > Bottom-Up/Call Tree/Event Log 就会显示出当前网站在语法解析/编译上的时间占比。如果你希望得到更完整的信息,那么可以打开 V8 的 Runtime Call Stats。在 Canary 中,其位于 Timeline 的 Experims > V8 Runtime Call Stats 下。

澳门金莎娱乐 26

image.png

编程语言原理

那么,首先让我们回顾一下编程语言原理。不管你使用什么编程语言,你需要一些软件来处理源代码以便让计算机能够理解。该软件可以是解释器,也可以是编译器。无论你使用的是解释型语言(JavaScript、Python、Ruby)还是编译型语言(c#、Java、Rust),都有一个共同的部分:将源代码作为纯文本解析为抽象语法树(abstract syntax tree, AST)的数据结构。

AST不仅以结构化的方式显示源代码,而且在语义分析中扮演着重要角色。在语义分析中,编译器验证程序和语言元素的语法使用是否正确。之后,使用 AST 来生成实际的字节码或者机器码。

抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。和抽象语法树相对的是具体语法树(concrete syntaxtree),通常称作分析树(parse tree)。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树。一旦 AST 被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。

你可以通过以下的几种方式来降低JavaScript的网络传输成本:

DeviceTiming

Etsy 的 DeviceTiming 工具能够模拟某些受限环境来评估页面的语法解析与执行时间。其将本地脚本包裹在了某个仪表工具代码内从而使我们的页面能够模拟从不同的设备中访问。可以阅读 Daniel Espeset 的Benchmarking JS Parsing and Execution on Mobile Devices 一文来了解更详细的使用方式。
澳门金莎娱乐 27

Chrome Tracing

打开 about:tracing 页面,Chrome 提供的底层的追踪工具允许我们使用disabled-by-default-v8.runtime_stats
来深度了解 V8 的时间消耗情况。V8 也提供了详细的指南来介绍如何使用这个功能。

澳门金莎娱乐 28

image.png

AST 程序

AST 不仅仅是用于语言解释器和编译器,在计算机世界中,它们还有多种应用。使用它们最常见的方法之一是进行静态代码分析。静态分析器不执行输入的代码,但是,他们仍然需要理解代码的结构。

例如,你可能想要实现一个工具,该工具可以找到公共代码结构,以便你可以重构它们以减少重复。你可能会通过使用字符串比较来实现这一点,但这个会相当简单且有局限性。

当然,如果你对实现这样的工具感兴趣,你不需要编写自己的解析器。有许多与 Ecmascript规范完全兼容的开源项目。Esprima和Acorn即是黄金搭档,还有许多工具可以帮助解析器生成输出,即ASTs,ASTs 被广泛应用于代码转换。

例如,你可能希望实现一个将 Python 代码转换为J avaScript 的转换器。基本思想是使用Python 转换器生成 AST,然后使用 AST 生成JavaScript代码。

你可能会觉得难以置信,事实是 ASTs 只是部分语言的不同表示法。在解析之前,它被表示为遵循一些规则的文本,这些规则构成了一种语言。在解析之后,它被表示为一个树结构,其中包含与输入文本完全相同的信息。因此,也可以进行反向解析然后回到文本。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具Fundebug。

  1. 只传送用户需要的代码。可用代码拆分(Code-splitting)。
  2. 优化压缩代码(ES5的Uglify,ES2015的babel-minify或者uglify-es)
  3. 高度压缩(用Brotli~q11,Zopfli或gzip)。Brotli的压缩比优于gzip。它可以帮CertSimple节省17%的压缩JS的字节大小,以及帮LinkedIn减少4%的加载时间。
  4. 移除无用的代码。用 Chrome DevTools代码覆盖率功能来查找未使用的JS代码。对于精简代码,可参阅tree-shaking, Closure Compiler的高端模式(advanced optimizations)和类似于 lodash-babel-plugin的微调库插件,或者像Moment.js这类库的Webpack的ContextReplacementPlugin。用babel-preset-env & browserlist来避免现代浏览器中已有的转译(transpiling)功能。高级开发人员可能会发现仔细分析Webpack打包(bundle)有助于他们识别和调整不必要的依赖关系。
  5. 缓存HTTP代码来减少网络传输量。确定脚本最佳的缓存时间(例如:max-age)和提供验证令牌(Etag)来避免传送无变化的字节。用Service Worker缓存一方面可以让应用程序网络更加灵活,另一方面也可以让你能够快速访问像V8代码缓存这样的功能。长期缓存可以去了解下Webpack带哈希值文件名(filename hashing)。

我们可以做些什么以降低 JavaScript 的解析时间?

  • 减少 JavaScript 包体体积。我们在上文中也提及,更小的包体往往意味着更少的解析工作量,也就能降低浏览器在解析与编译阶段的时间消耗。
  • 使用代码分割工具来按需传递代码与懒加载剩余模块。这可能是最佳的方式了,类似于PRPL这样的模式鼓励基于路由的分组,目前被 Flipkart, Housing.com 与 Twitter 广泛使用。
  • Script streaming: 过去 V8 鼓励开发者使用async/defer来基于script streaming实现 10-20% 的性能提升。这个技术会允许 HTML 解析器将相应的脚本加载任务分配给专门的 script streaming 线程,从而避免阻塞文档解析。V8 推荐尽早加载较大的模块,毕竟我们只有一个 streamer 线程。
  • 评估我们依赖的解析消耗。我们应该尽可能地选择具有相同功能但是加载地更快的依赖,譬如使用 Preact 或者 Inferno 来代替 React,二者相较于 React 体积更小具有更少的语法解析与编译时间。Paul Lewis 在最近的一篇文章中也讨论了框架启动的代价,与 Sebastian Markbage 的说法不谋而合:最好地评测某个框架启动消耗的方式就是先渲染一个界面,然后删除,最后进行重新渲染。第一次渲染的过程会包含了分析与编译,通过对比就能发现该框架的启动消耗。

如果你的 JavaScript 框架支持 AOT(ahead-of-time)编译模式,那么能够有效地减少解析与编译的时间。Angular 应用就受益于这种模式:
澳门金莎娱乐 29

WebPageTest

澳门金莎娱乐 30

image.png

WebPageTest 中 Processing Breakdown 页面在我们启用 Chrome > Capture Dev Tools Timeline 时会自动记录 V8 编译、EvaluateScript 以及 FunctionCall 的时间。我们同样可以通过指明disabled-by-default-v8.runtime_stats的方式来启用 Runtime Call Stats。

澳门金莎娱乐 31

image.png

JavaScript 解析

让我们看看 AST 是如何构建的。我们用一个简单的 JavaScript 函数作为例子:

function foo { if (x > 10) { var a = 2; return a * x; } return x + 10;}

解析器会产生如下的 AST:

澳门金莎娱乐 32

注意,为了观看方便,这里是解析器将生成的结果的简化版本。实际的 AST 要复杂得多。然而,这里的目的是为了运行源码之前的第一个步骤前。如果人想查看实际的 AST 是什么样子,可以访问AST Explorer。它是一个在线工具,你以在其中输入一些 JavaScript 并输出对应的 AST。

你可能会问,为什么需要知道 JavaScript解析器工作原理,毕竟这是浏览器工作,你想法是部分正确。下图展示了 JavaScript 执行过程中不同阶段的耗时。仔细瞅瞅,你或许会发现一些有趣的东西。

澳门金莎娱乐 33

发现没? 通常情况下,浏览器解析 JavaScript 大约需占总执行时间的15%20%。我没有具体统计过这些数值。这些是来自真实应用程序和以某种方式使用 JavaScript 的网站的统计数据。也许15%看起来不是很多,但相信我,这是很多。

一个典型的单页程序加载 0.4 mb 左右的 JavaScript,浏览器需要大约370ms来解析它。也许你会又说,这也不是很多嘛,本身花费的时间并不多。但请记住,这只是将 JavaScript 代码解析为 AST 所需要的时间。这并不包括运行本身的时间,也不包括在页面加载 ,如 CSS 和 HTML 渲染过程的耗时。这些还只涉及桌面,移动浏览器的情况会更加复杂,在手机上花在解析上的时间通常是桌面浏览器的 2 到 5 倍。

澳门金莎娱乐 34

上图显示了 1MB JavaScript 包在不同类的移动和桌面浏览器解析时间。

更重要的是,为了获得更多类原生的用户体验而把越来越多的业务逻辑堆积在前端,Web 应用程序正变得越来越复杂。你可以轻易地想到网络应用受到的性能影响。只需打开浏览器开发工具,然后使用该工具来解析、编译和浏览器中发生的所有其他事情上所消耗的时间。

澳门金莎娱乐 35

不幸的是,移动浏览器上没有开发者工具。不过不用担心,这并不意味着你对此无能为力。因为有DeviceTiming工具,它可以用来帮助检测受控环境中脚本的解析和运行时间。它通过插入代码来封装本地代码,这样每次从不同的设备访问页面时,就可以在本地测量解析和运行时间。

好事就是 JavaScript 引擎做了很多工作来避免冗余的工作,并得到了更好的优化,以下为主流浏览器使用的技术。

例如,V8 实现脚本流(script streaming)和代码缓存技术。脚本流即脚本一旦开始下载,asyncdeferred的 脚本就会在单独的线程上解析。这意味着在下载脚本完成后几乎立即完成解析,这会提升 10% 的页面加载速度。

每次访问页面时,JavaScript 代码通常编译为字节码。 然而,一旦用户访问另一页面,该字节码就被丢弃。 发生这种情况是因为编译后的代码很大程度上依赖于编译时机器的状态和上下文。 这是 Chrome 42 引入字节码缓存的原因。 该技术会本地缓存编译过的代码,这样当用户返回同一页面时,诸如下载,解析和编译等所有步骤都会被跳过。 这使得 Chrome 可以节省大约 40% 的解析和编译时间。 此外,这还可以节省移动设备的电量。

在 Opera 中,Carakan引擎可以重用另一个程序最近编译过的输出。没有要求代码必须来自相同的页面甚至同个域下。这种缓存技术实际上非常高效,还可以完全跳过编译步骤。它依赖于典型的用户行为和浏览场景:每当用户在应用程序/网站中遵循某个用户的特定浏览习惯,都会加载相同的 JavaScript 代码。不过,Carakan 引擎早已被谷歌的 V8 所取代。

Opera 新的 JavaScript 引擎 “Carakan”,目前速度是其他已存在 JavaScript 引擎(基于 SunSpider)的2.5倍。其在转化为本地机器代码时专门针对正则表达式做了优化。

Firefox 使用的SpiderMonkey引擎不会缓存所有内容。它可以过渡到监视阶段,在这个阶段中,它计算执行给定脚本的次数。基于此计算,它推导出频繁使用而可以被优化的代码部分。

SpiderMonkey 是 Mozilla 项目的一部分,是一个用 C 语言实现的 JavaScript 脚本引擎,另外还有一个叫做Rhino 的 Java 版本。

显然,有些人决定什么都不做。Safari 的首席开发人员Maciej Stachowiak表示,Safari 不会对编译后的字节码进行任何缓存。缓存技术他们是有考虑过的问题,但是他们还没有实现,因为生成代码的耗时小于总运行时间的 2%。

这些优化不会直接影响 JavaScript 源代码的解析,但是会尽可能完全避免。毕竟做总比没做好点?

我们可以做很多事情来改善应用程序的初始加载时间。最小化加载的 JavaScript 数量:代码越小、解析所需要时间就越少,运行时间也就越小。要做到这一点,我们只能在当前的路由上加载所需的代码,而不是加载一大陀的代码。例如,PRPL模式即表示该种代码传输类型。或者,可以检查代码的依赖关系,看看是否有什么冗余的依赖导致代码库膨胀,然而,这些东西需要很大的篇幅来进行讨论。

本文的主要的目的讨论作为 Web 开发人员可以做些什么来帮助 JavaScript 解析器更快地完成它的工作。还有,现代JavaScript 解析器使用启发法(heuristics)来决定是否立即运行指定的代码片段或者推迟在未来的某个时候运行。基于这些启发法,解析器将进行即时或懒解析。

启发法是针对模型求解方法而言的,是一种逐次逼近最优解的方法。这种方法对所求得的解进行反复判断实践修正直至满意为止。启发法的特点是模型简单,需要进行方案组合的个数少,因此便于找出最终答案。此方法虽不能保证得到最优解,但只要处理得当,可获得决策者满意的近似最优解。一般步骤包括:定义一个计算总费用的方法;报定判别准则;规定方案改选的途径;建立相应的模型;送代求解。

立即解析会运行需要立即编译的函数。它主要做三件事:构建 AST,构建作用域层级和查找所有语法错误。另一方面, 懒解析只运行未编译的函数。它不构建AST,也不查找所有语法错误,它只构建作用域层级,与立即解析相比节省了大约一半的时间。

显然,这不是一个新概念。即使像 IE 9 这样的浏览器也支持这种类型的优化,尽管与现在的解析器的工作方式相比,这种优化方式还很初级。

来看一个例子,假设有以下代码片段:

function foo() { function bar { return x + 10; } function baz { return x

  • y; } console.log(baz);}foo()

就像前面的例子一样,代码被输入到语法分析器中,语法分析器进行语法分析并输出AST,如下:

  • 声明函数foo

  • 调用函数foo

  • foo里声明函数bar接收参数x, 并返回x和 10 相加的结果

  • foo里声明函数baz接收参数xy, 并返回xy相加的结果

  • 调用baz函数传入 100 和 2。

  • 调用console.log参数为之前函数调用的返回值。

澳门金莎娱乐 36

那么期间发生了什么? 解析器看到bar函数的声明、baz函数的声明、bar函数的调用和console.log的调用。但是,解析器做了一些完全无关的额外工作即解析bar函数。为什么这无关紧要? 因为函数bar从来没有被调用过(或者至少在那个时候没有)。这是一个简单的示例,看起来可能有些不同寻常,但在许多实际应用程序中,许多声明的函数从未被调用。

澳门金莎娱乐 ,这里不解析bar函数,该函数声明了却没有调用它。只在需要的时候在函数运行前进行真正的解析。懒解析仍然需要找到函数的整个主体并为其声明,但仅此而已。它不需要语法树,因为它还没有被处理。另外,它不会从堆中分配内存,而堆通常会占用相当多的系统资源,简而言之,跳过这些步骤会带来很大的性能改进。

所以之前的例子,解析器实际上会像如下这样解析:

澳门金莎娱乐 37

注意,这里只确认bar函数声明,没有进入bar函数体。在这种情况下,函数体只是一个返回语句。但是,与大多数实际应用程序一样,它可以更大,包含多个返回语句、条件语句、循环、变量声明,甚至嵌套函数声明。这完全是在浪费时间和系统资源,因为这个函数永远不会被调用。

这是一个相当简单的概念,但实际上,它的实现是非常难的,不局限于以上示例。整个方法还可以适用于函数、循环、条件、对象等。基本上,所有需要解析的东西。

例如,下面是一个非常常见的 JavaScript 模式。

var myModule = (function() { // 整个模块的逻辑 // 返回模块对象})();

大多数现代 JavaScript 解析器都能识别这种模式,此模式表示代码需要立即解析。

那么为什么解析器不都使用懒解析呢? 如果懒解析某些代码,这些代码需要立即执行,这实际上会使代码运行速度变慢。需要运行一次懒解析之后进行另一个立即解析,这和立即解析相比,运行速度会慢 50%。

现在对解析器底层原理有了大致的了解,是时候考虑如何提高解析器的解析速度。可以用这种方式编写代码,以便在正确的时间解析函数。大多数解析器都能识别一种模式:使用括号封装函数。对于解析器来说,这几乎总是一个积极的信号,即函数需要立即执行。如果解析器看到一个左括号,紧接着是一个函数声明,它将立即解析这个函数。可以通过显式地声明立即执行的函数来帮助解析器加快解析速度。

假设有一个名为foo的函数。

function foo { return x * 10;}

因为没有明显地标识表明需要立即运行该函数所以浏览器会进行懒解析。然而,我们确定这是不对的,那么可以运行两个步骤。

首先,将函数存储在一个变量中:

var foo = function foo { return x * 10;};

注意,这里有使用函数的名称foo,这不是必需的,但是建议这样做,因为在抛出异常的情况下,stacktrace 会保留实际函数名称,而不仅仅是<anonymous>

以上事例解析器执行懒解析,可以用括号封装起来,让解析器进行立即解析:

var foo = (function foo { return x * 10;});

现在,解析器看见function关键字前的左括号便会立即进行解析。

因为需要知道解析器在哪些情况下执行懒解析或者立即解析,所以很难手动管理。此外,还需要花时间考虑是否立即调用某个函数,肯定没人想这么做的。

最后,这种地让代码更难阅读和理解。可以使用Optimize.js可以帮我们做这类事情,该工具只是用来优化 JavaScript 源代码的初始加载时间,它们对代码进行静态分析,然后通过使用括号封装需要立即运行的函数以便浏览器立即解析并准备运行它们。

像往常一样编码,然后有一段代码看起来像这样的:

(function() { console.log('Hello, World!');})();

一切看起来都很好,如预期的那样工作,而且速度很快,因为在函数声明之前添加左括号。当然,在进入生产环境之前需要进行代码压缩,以下为压缩工具的输出:

!function(){console.log('Hello, World!')}();

好像没问题,代码像以前一样工作。但是好像少了什么,压缩工具删除包裹函数的括号,而是在函数前放置了一个感叹号,这意味着解析器将跳过此并将执行惰解析。

最重要的是,为了能够执行该函数,它将在懒解析之后立即进行立即解析。 这会使代码运行得更慢,幸运的是,可以利用Optimize.js来解决此类问题,传给Optimize.js压缩过的代码会输出如下代码:

!(function(){console.log('Hello, World!')})();

这还差不多,现在拥有两全其美方案:压缩代码且解析器正确地识别懒解析和立即解析的函数。

澳门金莎娱乐 38
(减少向用户发送JavaScript量的最佳做法。)

现代浏览器是如何提高解析与编译速度的?

不用灰心,你并不是唯一纠结于如何提升启动时间的人,我们 V8 团队也一直在努力。我们发现之前的某个评测工具 Octane 是个不错的对于真实场景的模拟,它在微型框架与冷启动方面很符合真实的用户习惯。而基于这些工具,V8 团队在过去的工作中也实现了大约 25% 的启动性能提升:
澳门金莎娱乐 39

本部分我们就会对过去几年中我们使用的提升语法解析与编译时间的技巧进行阐述。

User Timing

我们还可以使用 Nolan Lawson 推荐的User Timing API来评估语法解析的时间。不过这种方式可能会受 V8 预解析过程的影响,我们可以借鉴 Nolan 在 optimize-js 评测中的方式,在脚本的尾部添加随机字符串来解决这个问题。我基于 Google Analytics 使用相似的方式来评估真实用户与设备访问网站时候的解析时间:

澳门金莎娱乐 40

image.png

预编译

但为什么不能在服务器端完成所有这些工作呢? 毕竟,最好这样做一次并将结果提供给客户端,而不强制各个客户端重复做该项事情。那么,目前正在讨论引擎是否应该提供一种执行预编译脚本的方法,这样就可以节省浏览器运行时间。

从本质上讲,该思路是拥有可以生成字节码的务器端工具,这样只需要传输字节码并在客户端运行,之后会看到启动时间的一些主要差异。 这可能听起来很诱人,但事情并非那么简单,还可能会产生相反的效果,因为它会更大,并且很可能需要签署代码并出于安全原因对其进行处理。 例如,V8 团队正在努力解决重复解析问题,这样预编译有可能实际并没有多大的用处。

解析/编译

代码缓存

澳门金莎娱乐 41

Chrome 42 开始引入了所谓的代码缓存的概念,为我们提供了一种存放编译后的代码副本的机制,从而当用户二次访问该页面时可以避免脚本抓取、解析与编译这些步骤。除以之外,我们还发现在重复访问的时候这种机制还能避免 40% 左右的编译时间,这里我会深入介绍一些内容:

  • 代码缓存会对于那些在 72 小时之内重复执行的脚本起作用。
  • 对于 Service Worker 中的脚本,代码缓存同样对 72 小时之内的脚本起作用。
  • 对于利用 Service Worker 缓存在 Cache Storage 中的脚本,代码缓存能在脚本首次执行的时候起作用。

总而言之,对于主动缓存的 JavaScript 代码,最多在第三次调用的时候其能够跳过语法分析与编译的步骤。我们可以通过chrome://flags/#v8-cache-strategies-for-cache-storage来查看其中的差异,也可以设置js-flags=profile-deserialization运行 Chrome 来查看代码是否加载自代码缓存。不过需要注意的是,代码缓存机制仅会缓存那些经过编译的代码,主要是指那些顶层的往往用于设置全局变量的代码。而对于类似于函数定义这样懒编译的代码并不会被缓存,不过 IIFE 同样被包含在了 V8 中,因此这些函数也是可以被缓存的。

DeviceTiming

Etsy 的 DeviceTiming 工具能够模拟某些受限环境来评估页面的语法解析与执行时间。其将本地脚本包裹在了某个仪表工具代码内从而使我们的页面能够模拟从不同的设备中访问。可以阅读 Daniel Espeset 的Benchmarking JS Parsing and Execution on Mobile Devices 一文来了解更详细的使用方式。

澳门金莎娱乐 42

image.png

提升编译速度一些建议

  • 检查依赖,减少不必要的依赖

  • 分割代码为更小的块而不是一整陀的

  • 尽可能推迟加载 JavaScript,按需要加载或者动态加载。

  • 使用开发者工具和 DeviceTiming 来检测性能瓶颈

  • 用像 Optimize.js 的工具来帮助解析器选择立即解析或者懒解析以加快解析速度

原文:How JavaScript works: Parsing, Abstract Syntax Trees + 5 tips on how to minimize parse time

下载成功后,JavaScript**绝大部分的时间都消耗在JS引擎对下载代码的解析/编译**上。在Chrome DevTools中,解析和编译是下面性能面板(Performance panel)中黄色“脚本”时间的一部分。

Script Streaming

Script Streaming允许在后台线程中对异步脚本执行解析操作,可以对于页面加载时间有大概 10% 的提升。上文也提到过,这个机制同样会对同步脚本起作用。
澳门金莎娱乐 43这个特性倒是第一次提及,因此 V8 会允许所有的脚本,即使阻塞型的<script src=''>脚本也可以由后台线程进行解析。不过缺陷就是目前仅有一个 streaming 后台线程存在,因此我们建议首先解析大的、关键性的脚本。在实践中,我们建议将<script defer>添加到<head>块内,这样浏览器引擎就能够尽早地发现需要解析的脚本,然后将其分配给后台线程进行处理。我们也可以查看 DevTools Timeline 来确定脚本是否被后台解析,特别是当你存在某个关键性脚本需要解析的时候,更需要确定该脚本是由 streaming 线程解析的。
澳门金莎娱乐 44

我们可以做些什么以降低 JavaScript 的解析时间?

1.减少 JavaScript 包体体积。我们在上文中也提及,更小的包体往往意味着更少的解析工作量,也就能降低浏览器在解析与编译阶段的时间消耗。
2.使用代码分割工具来按需传递代码与懒加载剩余模块。这可能是最佳的方式了,类似于PRPL这样的模式鼓励基于路由的分组,目前被 Flipkart, Housing.com 与 Twitter 广泛使用。
3.Script streaming: 过去 V8 鼓励开发者使用async/defer
来基于script streaming实现 10%-20% 的性能提升。这个技术会允许 HTML 解析器将相应的脚本加载任务分配给专门的 script streaming 线程,从而避免阻塞文档解析。V8 推荐尽早加载较大的模块,毕竟我们只有一个 streamer 线程。
4.评估我们依赖的解析消耗。我们应该尽可能地选择具有相同功能但是加载地更快的依赖,譬如使用 Preact 或者 Inferno 来代替 React,二者相较于 React 体积更小具有更少的语法解析与编译时间。Paul Lewis 在最近的一篇文章中也讨论了框架启动的代价,与 Sebastian Markbage 的说法不谋而合:最好地评测某个框架启动消耗的方式就是先渲染一个界面,然后删除,最后进行重新渲染。第一次渲染的过程会包含了分析与编译,通过对比就能发现该框架的启动消耗。

如果你的 JavaScript 框架支持 AOT(ahead-of-time)编译模式,那么能够有效地减少解析与编译的时间。Angular 应用就受益于这种模式:

澳门金莎娱乐 45

image.png

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了9亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用!

澳门金莎娱乐 46

澳门金莎娱乐 47

语法解析 & 编译优化

我们同样致力于打造更轻量级、更快的解析器,目前 V8 主线程中最大的瓶颈在于所谓的非线性解析消耗。譬如我们有如下的代码片:

JavaScript

(function (global, module) { … })(this, function module() { my functions })

1
(function (global, module) { … })(this, function module() { my functions })

V8 并不知道我们编译主脚本的时候是否需要module这个模块,因此我们会暂时放弃编译它。而当我们打算编译module时,我们需要重分析所有的内部函数。这也就是所谓的 V8 解析时间非线性的原因,任何一个处于 N 层深度的函数都有可能被重新分析 N 次。V8 已经能够在首次编译的时候搜集所有内部函数的信息,因此在未来的编译过程中 V8 会忽略所有的内部函数。对于上面这种module形式的函数会是很大的性能提升,建议阅读The V8 Parser(s) — Design, Challenges, and Parsing JavaScript Better来获取更多内容。V8 同样在寻找合适的分流机制以保证启动时能在后台线程中执行 JavaScript 编译过程。

现代浏览器是如何提高解析与编译速度的?

不用灰心,你并不是唯一纠结于如何提升启动时间的人,我们 V8 团队也一直在努力。我们发现之前的某个评测工具 Octane 是个不错的对于真实场景的模拟,它在微型框架与冷启动方面很符合真实的用户习惯。而基于这些工具,V8 团队在过去的工作中也实现了大约 25% 的启动性能提升:

澳门金莎娱乐 48

image.png

本部分我们就会对过去几年中我们使用的提升语法解析与编译时间的技巧进行阐述。

版权声明

转载时请注明作者Fundebug以及本文地址:

Bottom-Up/Call Tree允许我们去确切地查看解析/编译所用时间:

预编译 JavaScript?

每隔几年就有人提出引擎应该提供一些处理预编译脚本的机制,换言之,开发者可以使用构建工具或者其他服务端工具将脚本转化为字节码,然后浏览器直接运行这些字节码即可。从我个人观点来看,直接传送字节码意味着更大的包体,势必会增加加载时间;并且我们需要去对代码进行签名以保证能够安全运行。目前我们对于 V8 的定位是尽可能地避免上文所说的内部重分析以提高启动时间,而预编译则会带来额外的风险。不过我们欢迎大家一起来讨论这个问题,虽然 V8 目前专注于提升编译效率以及推广利用 Service Worker 缓存脚本代码来提升启动效率。我们在 BlinkOn7 上与 Facebook 以及 Akamai 也讨论过预编译相关内容点击预览。

代码缓存

澳门金莎娱乐 49

image.png

Chrome 42 开始引入了所谓的代码缓存的概念,为我们提供了一种存放编译后的代码副本的机制,从而当用户二次访问该页面时可以避免脚本抓取、解析与编译这些步骤。除以之外,我们还发现在重复访问的时候这种机制还能避免 40% 左右的编译时间,这里我会深入介绍一些内容:

1.代码缓存会对于那些在 72 小时之内重复执行的脚本起作用。
2.对于 Service Worker 中的脚本,代码缓存同样对 72 小时之内的脚本起作用。
3.对于利用 Service Worker 缓存在 Cache Storage 中的脚本,代码缓存能在脚本首次执行的时候起作用。
总而言之,对于主动缓存的 JavaScript 代码,最多在第三次调用的时候其能够跳过语法分析与编译的步骤。我们可以通过chrome://flags/#v8-cache-strategies-for-cache-storage来查看其中的差异,也可以设置?js-flags=profile-deserialization运行 Chrome 来查看代码是否加载自代码缓存。不过需要注意的是,代码缓存机制仅会缓存那些经过编译的代码,主要是指那些顶层的往往用于设置全局变量的代码。而对于类似于函数定义这样懒编译的代码并不会被缓存,不过 IIFE 同样被包含在了 V8 中,因此这些函数也是可以被缓存的。

澳门金莎娱乐 50

Optimize JS 优化

类似于 V8 这样的 JavaScript 引擎在进行完整的解析之前会对脚本中的大部分函数进行预解析,这主要是考虑到大部分页面中包含的 JavaScript 函数并不会立刻被执行。
澳门金莎娱乐 51

预编译能够通过只处理那些浏览器运行所需要的最小函数集合来提升启动时间,不过这种机制在 IIFE 面前却反而降低了效率。尽管引擎希望避免对这些函数进行预处理,但是远不如optimize-js这样的库有作用。optimize-js 会在引擎之前对于脚本进行处理,对于那些立即执行的函数插入圆括号从而保证更快速地执行。这种预处理对于 Browserify, Webpack 生成包体这样包含了大量即刻执行的小模块起到了非常不错的优化效果。尽管这种小技巧并非 V8 所希望使用的,但是在当前阶段不得不引入相应的优化机制。

Script Streaming

Script Streaming允许在后台线程中对异步脚本执行解析操作,可以对于页面加载时间有大概 10% 的提升。上文也提到过,这个机制同样会对同步脚本起作用。

澳门金莎娱乐 52

image.png

这个特性倒是第一次提及,因此 V8 会允许所有的脚本,即使阻塞型的<script src=''>脚本也可以由后台线程进行解析。不过缺陷就是目前仅有一个 streaming 后台线程存在,因此我们建议首先解析大的、关键性的脚本。在实践中,我们建议将<script defer>添加到<head>块内,这样浏览器引擎就能够尽早地发现需要解析的脚本,然后将其分配给后台线程进行处理。我们也可以查看 DevTools Timeline 来确定脚本是否被后台解析,特别是当你存在某个关键性脚本需要解析的时候,更需要确定该脚本是由 streaming 线程解析的。

澳门金莎娱乐 53

image.png

(Chrome DevTools性能面板下级菜单>Bottom-U。启动V8的Runtime Call Stats,就能看到不同阶段的时间消耗,比如解析/编译所用时间。)

总结

启动阶段的性能至关重要,缓慢的解析、编译与执行时间可能成为你网页性能的瓶颈所在。我们应该评估页面在这个阶段的时间占比并且选择合适的方式来优化。我们也会继续致力于提升 V8 的启动性能,尽我所能!

语法解析 & 编译优化

我们同样致力于打造更轻量级、更快的解析器,目前 V8 主线程中最大的瓶颈在于所谓的非线性解析消耗。譬如我们有如下的代码片:

(function (global, module) { … })(this, function module() { my functions })
V8 并不知道我们编译主脚本的时候是否需要module
这个模块,因此我们会暂时放弃编译它。而当我们打算编译module
时,我们需要重分析所有的内部函数。这也就是所谓的 V8 解析时间非线性的原因,任何一个处于 N 层深度的函数都有可能被重新分析 N 次。V8 已经能够在首次编译的时候搜集所有内部函数的信息,因此在未来的编译过程中 V8 会忽略所有的内部函数。对于上面这种module
形式的函数会是很大的性能提升,建议阅读The V8 Parser(s)?—?Design, Challenges, and Parsing JavaScript Better来获取更多内容。V8 同样在寻找合适的分流机制以保证启动时能在后台线程中执行 JavaScript 编译过程。

但是,这为什么会是个问题?

延伸阅读

  • Planning for Performance
  • Solving the Web Performance Crisis by Nolan Lawson
  • JS Parse and Execution Time
  • Measuring Javascript Parse and Load
  • Unpacking the Black Box: Benchmarking JS Parsing and Execution on Mobile Devices (slides)
  • When everything’s important, nothing is!
  • The truth about traditional JavaScript benchmarks
  • Do Browsers Parse JavaScript On Every Page Load

    1 赞 1 收藏 评论

澳门金莎娱乐 54

预编译 JavaScript?

每隔几年就有人提出引擎应该提供一些处理预编译脚本的机制,换言之,开发者可以使用构建工具或者其他服务端工具将脚本转化为字节码,然后浏览器直接运行这些字节码即可。从我个人观点来看,直接传送字节码意味着更大的包体,势必会增加加载时间;并且我们需要去对代码进行签名以保证能够安全运行。目前我们对于 V8 的定位是尽可能地避免上文所说的内部重分析以提高启动时间,而预编译则会带来额外的风险。不过我们欢迎大家一起来讨论这个问题,虽然 V8 目前专注于提升编译效率以及推广利用 Service Worker 缓存脚本代码来提升启动效率。我们在 BlinkOn7 上与 Facebook 以及 Akamai 也讨论过预编译相关内容。

澳门金莎娱乐 55

Optimize JS 优化

类似于 V8 这样的 JavaScript 引擎在进行完整的解析之前会对脚本中的大部分函数进行预解析,这主要是考虑到大部分页面中包含的 JavaScript 函数并不会立刻被执行。

澳门金莎娱乐 56

image.png

预编译能够通过只处理那些浏览器运行所需要的最小函数集合来提升启动时间,不过这种机制在 IIFE 面前却反而降低了效率。尽管引擎希望避免对这些函数进行预处理,但是远不如optimize-js这样的库有作用。optimize-js 会在引擎之前对于脚本进行处理,对于那些立即执行的函数插入圆括号从而保证更快速地执行。这种预处理对于 Browserify, Webpack 生成包体这样包含了大量即刻执行的小模块起到了非常不错的优化效果。尽管这种小技巧并非 V8 所希望使用的,但是在当前阶段不得不引入相应的优化机制。

耗费很长的时间在解析/编译代码上,会严重延迟用户与你网站的交互时间。你发送的JavaScript越多,在网站实现交互前所用的解析/编译的时间就会越长。

总结

启动阶段的性能至关重要,缓慢的解析、编译与执行时间可能成为你网页性能的瓶颈所在。我们应该评估页面在这个阶段的时间占比并且选择合适的方式来优化。我们也会继续致力于提升 V8 的启动性能,尽我所能!

澳门金莎娱乐 57

延伸阅读

Planning for Performance
Solving the Web Performance Crisis by Nolan Lawson
JS Parse and Execution Time
Measuring Javascript Parse and Load
Unpacking the Black Box: Benchmarking JS Parsing and Execution on Mobile Devices (slides)
When everything’s important, nothing is!
The truth about traditional JavaScript benchmarks
Do Browsers Parse JavaScript On Every Page Load

查看英文原文:JavaScript Start-up Performance

infoQ中文出处:JavaScript 启动性能瓶颈分析与解决方案

前端·哈达
好好学习,天天向上

即使是同样多的字节,浏览器处理JavaScript也会比处理等大小的图片和网页字体消耗更高的成本——Tom Dale

相比于JavaScript,处理等字节的图片所需要的时间成本很高(因为图片仍需要解码!)但是在一般的移动设备上,反而是JS更有可能对页面的交互产生负面的影响。

澳门金莎娱乐 58

(JavaScript字节和图像字节耗费的时间成本不同。图像通常不会阻塞主线程,也不会在解码和光栅化的时候阻止接口进行交互。然而JS会因为解析、编译和执行的时间消耗阻滞交互性。)

当我们说解析和编译的速度变慢的时候,要注意具体的网络端和设备端的情况,在这里我们针对的是普通手机。普通用户所使用手机的CPU和GPU速度比较慢,没有L2/L3缓存,甚至可能会有内存限制。

网络功能和设备功能并不总是相匹配的。有速度惊人的光纤连接的用户不一定会有最好的CPU来解析和评估发送到他们的设备的JavaScript。反过来也是如此…你可能有糟糕的网络连接,但却有快速的CPU。 – Kristofer Baxter,LinkedIn

在JavaScript Start-up Performance一文中,我曾提到过在低端和高端硬件上解析~1MB解压缩过(简单)的JavaScript所需要消耗的时间。市面上的普通手机和运行速度最快的手机相比,解析/编译代码的所用的时间会有2-5倍的差距。

澳门金莎娱乐 59

(在不同级别的台式和移动设备上解析1MB的JavaScript包(经gzip压缩,大小约为250KB )。当分析解析成本时,我们需要考虑的是解压后的数据量,例如〜250KBgzip压缩过的JS解压缩后约为〜1MB的代码。)

那解析/编译真实网站的时间差异又会是怎样呢,比如CNN.com网站?

在高端的iPhone 8上解析/编译CNN网站的JS大约花费了4秒,相比于普通手机(Moto G4)的13秒左右。这可以显著地影响用户与CNN网站实现完全交互的速度。

澳门金莎娱乐 60

(苹果公司的A11仿生芯片和更普通的Android硬件中的Snapdragon 617的解析时间上的性能比较

这就突出了在普通硬件(比如Moto G4)上测试的重要性,而不仅仅是在自己恰好有的手机上测试。基于自己客户原有的设备和网络条件来进行优化是很重要的。

澳门金莎娱乐 61

分析可以使你更加深入了解自己真实客户访问网站所用的移动设备的级别和这些设备CPU/GPU的局限性。

我们真的发送了太多的JavaScript了吗?呃…真有可能:)

用HTTP Archive(qian500K站点)来分析移动设备上JavaScript的状态时,我们可以看到,50%的站点需要14秒才能取得交互。这些网站光是用来解析和编译JS的时间就长达4秒。

澳门金莎娱乐 62

考虑到获取和处理JS和其他资源所耗费的时间,也就不奇怪用户可能需要在页面可用之前等待一段时间了。我们绝对可以在这个方面做的更好。

从网页中删除不必要的JavaScript可以减少传输时间、CPU密集型解析和编译以及潜在的内存消耗,同时也有助于加快网页的交互速度。

执行时间

不光是解析和编译会有时间成本。执行JavaScript(在解析/编译之后运行代码)也是需要在主线程上进行的操作之一。长的执行时间也会延迟用户与你网站的交互时间。

澳门金莎娱乐 63

如果脚本执行的时间超过了50ms,那么延迟交互的时间将会是下载、编译和执行JS所需时间的总和——Alex Russell

为了解决这个问题,可以将JavaScript脚本分为几个小块来执行,以避免锁定主线程。探索一下是否可以减少脚本执行过程中进行中工作量的可能性。

减少JavaScript交付成本的模式

当你试图降低JavaScript解析/编译和网络传输所用时间时,类似于基于路由分块和PRPL这些模式也会有用。

PRPL是一种通过激进的(aggresive)代码分割和缓存来优化交互性的模式:

澳门金莎娱乐 64

为了能将PRPL的影响以视觉化方式表现出来。

我们用V8引擎中的Runtime Call Stats分析了流行移动网站和progressive Web Apps(PWA)的加载时间。正如我们所看到的,解析部分(用橙色表示)是很多网站页面加载时产生显著时间消耗的部分。

澳门金莎娱乐 65

Wego网站就使用了PRPL来保持较低的路由解析时间,让页面交互得以快速的进行。以上的很多站点都试图采用代码分割和性能预算来降低JS的消耗。

JavaScript的其他消耗

JavaScript还可以通过其他方式来影响页面性能:

  • 存储。页面可能会因为垃圾回收(GC,garbage colleciton),页面可能会出现画面中断卡顿(junk)和暂停。因为当一个浏览器回收内存的时候,JS的执行也会被中止,所以经常回收垃圾的浏览器会比我们想象中的更频繁地中止JS的执行。在这种情况下,可以通过避免内存溢出和频繁内存回收来保持页面的流畅。
  • 在运行时,长时间的运行JavaScript会阻塞主线程,导致页面没有响应。这种情况下,可以将脚本的工作量分成多个小的板块(具体可用requestAnimationFrame()或者requestIdleCallback()进行任务调度)来执行,以此减少页面响应的问题。

Progressive Bootstrapping

很多网站将优化内容可视性作为保证交互性所需代价的一部分。为了在JavaScript有大体积包体时改善首屏性能,开发人员有时会先用服务器端渲染帮助客户提前看到页面内容,然后再在JavaScript最终执行完成后“升级”附加上事件处理程序。

但是值得注意的是,这样做也是有代价的。你1)通常会发送一个更大的HTML响应来增加交互性,2)在一段时间内,用户会处在一半的页面交互体验缺失的奇怪状况下,直到JavaScript处理完成。

Progressive Bootstrapping或许会是一个更好的处理方式。浏览器请求一个最少化的功能页面(仅由当前路由所需要的HTML/JS/CSS组成)。当有更多的资源请求的时候,应用程序则可以懒加载(lazy-load)和解锁更多的功能。

澳门金莎娱乐 66
Progressive Bootstrapping visual by Paul Lewis

仅加载可视区域内的代码是其中的关键。PRPL和Progressive Bootstrapping模式均可以用来实现这一点。

结论

传输脚本的大小对低端网络至关重要,而解析时间对于CPU有局限性的设备很重要。降低传输脚本的大小和减少解析消耗时间是有必要的。

有团队发现采用严格的性能预算可以成功降低他们JavaScript的传输和解析/编译的时间消耗。

澳门金莎娱乐 67

(考虑一下在我们所做的架构决策下,JS有多大的空间可以让我们的应用程序具有逻辑)

如果你正在建一个用于移动设备上的站点,请尽可能的在代表性硬件上开发,保持较低的JavaScript解析/编译的时间成本,并采用性能预算来确保团队对自身JavaScript的成本关注。

【编辑推荐】

版权声明:本文由澳门金莎娱乐发布于新闻资讯,转载请注明出处:澳门金莎娱乐抽象语法树,启动性能瓶颈分析与解决方案