导航菜单

Google Chrome 工程师:JavaScript 不容错过的八大优化建议

R6Ay3Xl4IFwSVm

[摘要]本文是PerfMatters 2019网络性能大会上谷歌Chrome团队的开发项目工程师Addy Osmani的“JavaScript性能优化”。它共享用于处理JavaScript的脚本优化建议,大大减少了下载时间和执行时间。

视频地址:科学需要在线)

RVd1n3f7CWFugp

作者| Addy Osmani

翻译|苏本如编辑|涂敏

制作| CSDN(ID:CSDNnews)

以下是翻译:

在过去几年中,由于改进了脚本解析和浏览器的编译速度,Javascript的成本结构发生了巨大变化。到2019年,处理Javascript的开销主要体现在脚本下载时间和CPU执行时间上。

如果浏览器的主线程忙于执行Javascript脚本,则可能会影响用户交互体验,因此优化脚本执行时间和消除网络瓶颈会对用户体验产生积极影响。

RT4Gwk56bt5iOJ

高级实用指南

这对Web开发人员意味着什么?这意味着Parse和Compile不再像我们想象的那么慢。因此,在优化Javascript包时,开发人员应该关注以下三个方面:

减少下载时间

确保的Javascript包尽可能地小,特别是对于移动设备。较小的包可以提升下载速度,降低内存使用量,并减少CPU开销。

避免只有一个大的Javascript包;如果包大小超过50-100 KB,就将其拆分为几个小包。(借助HTTP/2协议的多路复用机制,多个请求和响应消息可以同时传输,从而减少额外请求的开销。)

对于移动设备上使用的Java脚本包更要尽可能地小,一方面因为网络带宽的制约,另一方面需要要尽量减少内存的使用。

缩短执行时间

避免持续占用主线程并影响页面响应时间的长时任务,现在脚本下载后的执行时间成为主要的成本开销。

避免使用大型内联脚本(因为它们仍然需要在主线程上进行解析和编译)。

经验法则:如果一个脚本超过1KB,就不要将其内联(因为当外部脚本大小超过1KB时,就会触发代码缓存)

RT7S2kzFTfre26

为什么下载和执行时间很重要?

为什么优化下载和执行时间对我们很重要?因为对于低端网络而言,下载时间的影响非常之大。尽管4G(甚至5G)在全球范围内增长迅速,但大多数人的有效连接速度仍然远远低于网络的标称速度。有时当我们外出时,会感觉到网速下降到只有3G的速度(甚至更糟)。

的JavaScript的执行时间对于CPU较慢的低端手机也非常重要。由于CPU,GPU,和散热限制的不同,高端和低端手机的性能差距巨大。这对JavaScript的的性能影响明显,因为它的执行受到CPU性能的制约。

XX实际上,在Chrome等浏览器上,JavaScript占用的总页面加载时间可能高达30%。下图是在高端台式PC上对具有典型工作负载(Reddit.com)的网站进行页面加载分析:

RVd1n43BSb9LQq

V8引擎下的Javascript处理时间占整个页面加载时间的10-30%

对于移动设备,在中端手机(如Moto G4)上实施Reddit的Javascript脚本需要比在高端手机(如Pixel 3)上长3-4倍,而在低端手机上(价格低于100美元)实施Reddit在Alcatel 1X上的Javascript脚本需要花费6倍的时间:

RVd1n4IhSX7Yt

Reddit的Javascript脚本在几个不同设备(低,中,高)上的执行时间。

注意:Reddit对桌面和移动网络有不同的体验,因此无法将MacBook Pro执行结果与其他结果进行比较。

当您努力优化JavaScript执行时,您需要了解可能长期主宰UI线程的长期任务。即使页面似乎已加载,这些长期任务也可能会拖累关键任务的执行。将长期任务分解为较小的任务。通过拆分代码并确定加载顺序,您可以更快地实现页面交互,并可能减少输入延迟。

RVd1n4dFYZDNSn

应该分割垄断主线程的长期任务。

RTJXJ1kBqzfCnu

V8引擎如何提高Javascript解析/编译的速度?

自Chrome版本60以来,V8引擎的原始JS的分辨率提高了两倍。与此同时,Chrome还做了其他工作来并行化解析和编译,这使得这部分成本开销对用户体验的影响不那么显着和关键。

通过将解析和编译工作转移到工作线程,V8引擎将主线程上的解析和编译工作量平均减少了40%。例如,Facebook下降了46%,Pinterest下降了62%,最大的改善是YouTube,下降了81%。这是基于现有的非主线程流解析/编译性能改进的进一步改进。

RVd1n4xsgu0u8

分析不同版本的V8发动机的分辨率时间

我们还可以说明不同V8引擎对CPU处理时间的不同CPU版本的影响。如您所见,Chrome 61解析Facebook的JS脚本所需的时间允许Chrome 75解析相同的Facebook JS脚本和六个Twitter JS脚本。

RVd1nHU7LKv89d

Chrome 61解析Facebook的JS脚本所需的时间允许Chrome 75解析相同的Facebook JS脚本和六个Twitter JS脚本。

让我们仔细看看如何实现这些改进。通常,脚本资源可以在工作线程上进行流式处理和编译,这意味着:

V8引擎可以在不阻塞主线程的情况下解析和编译JavaScript。

当整个HTML解析器遇到标记时,就开始流式处理。遇到阻塞解析器(解析阻挡)的脚本时,HTML解析器就放弃,而对于异步脚本则继续处理。

在大多数网络连接速度下,V8引擎的解析速度都比下载速度快,因此在最后一个脚本字节被下载后几毫秒的时间内,V8引擎就能完成解析+编译工作。

具体来说,很多老版本的铬在开始脚本解析之前,需要将脚本下载完成,这是一种简单的方法,但它没有充分利用CPU的能力。而从版本41到68,浏览器在下载一开始时就立即在单独的线程上解析异步和延迟脚本。

RVd1nHoASaDMS6

JS脚本以多个块下载.V8引擎看到大于30KB的脚本被下载后就会启动脚本流解析工作。

Chrome 71采用了基于任务(任务型)的设置方案。调度器可以一次解析多个异步/延迟脚本,这一改进使得主线程解析时间缩短了约20%,真实网站上的TTI/FID整体提高了大约2%。

RVd1nI7BMfLkQX

Chrome 71采用了基于任务(任务型)的设置,调度器可以一次解析多个异步/延迟脚本

Chrome 72开始采用流式处理作为主要的解析方式,现在常规的同步脚本(内联脚本除外)也可以采用这种解析方式。如果主线程需要,我们也可以继续采用基于任务的解析,从而减少不必要地重复工作。

旧版的铬支持流式解析和编译,其中来自网络的脚本源数据必须先到达铬主线程后,再转发给流解析器解析。

XX

这通常会导致脚本数据从网络下载,但由于主线程上的其他任务(例如HTML解析,排版或JavaScript执行),阻止脚本数据的转发,流解析器(流解析器))必须等待。

现在我们正在尝试在预加载时开始解析,而之前的主线程反弹将阻碍此操作。

Leszek Swirski关于BlinkOn 10的演讲介绍了相关细节:科学需要在线)

RTJXJ7YR5xGDl

这些变化如何反映在DevTools中?

除了上述内容之外,DevTools还存在一个问题,即以一种表明它将独占CPU(完全阻塞)的方式呈现整个解析器任务。但是,无论解析器是否需要数据(数据需要通过主线程),它都会阻塞。当我从单个流线程移动到多个流式传输任务时,这个问题变得明显。以下是您在Chrome 69中看到的内容:

RVd1nIQD51Rf2V

DevTools以一种表明它将独占CPU(完全阻塞)的方式呈现整个解析器任务

如上所示,“解析脚本”任务需要1.08秒。但解析JavaScript实际上并不那么慢!大多数情况下,除了等待数据通过主线程之外什么也做不了。

Chrome 76中显示的内容不同:

RVd1nIg6NIh3ZP

在Chrome 76中,解析工作被分解为多个较小的流任务。

通常,DevTools性能窗格非常适合在宏级别分析页面。对于更具体的V8指标,例如Javascript解析和编译时间,我们建议使用Chrome跟踪工具和运行时呼叫统计(RCS)。在RCS结果中,Parse-Background和Compile-Background将告诉您在主线程之外解析和编译Javascript所需的时间,而Parse和Compile是主线程的度量。

RVd1nXC254H8MmRTJXJ7uHXU5GZc

这些变化对实际应用程序有何影响?

让我们看一下真实网站的一些例子,看看脚本流是如何工作的。

RVd1nXXICKIy8w

花在主线程和工作线程上的时间解析和编译MacBook Pro上的Reddit网站的JS

Reddit.com有几个超过100KB的JK包,它们包含在外部函数中,导致在主线程上进行大量的惰性编译。如上图所示,主线程时间确实很关键,因为主线程继续忙将严重影响交互体验。 Reddit将大部分时间花在主线程上,而工作线程或后台线程的使用率较低。

您可以将一些较大的JS包拆分为不需要包装的小包(例如,每个包50 KB),以最大化并行化,以便可以单独解析和编译每个包。在加载过程中减少主线程的解析/编译时间。

RVd1nXmC7ei4wr

花在主线程和工作线程上的时间解析和编译MacBook Pro上的Facebook站点的JS

我们来看看像facebook.com这样的网站。 Facebook使用大约292个请求并加载大约6MB的压缩JS脚本,其中一些是异步的,一些是预加载的,一些是低优先级的。他们的许多脚本非常小而且不是非常精细,这有助于后台/工作线程的整体并行性,因为这些较小的脚本可以同时进行流解析/编译。

值得注意的是,Facebook或Gmail等经验丰富的应用程序的桌面版本上有太多脚本可能是合理的。但您的网站可能与Facebook不同。在任何情况下,尽可能简化您的JS包,如果您不需要它,请不要加载它。

虽然大多数JavaScript解析和编译工作都可以在后台线程上完成,但仍然有一些工作必须在主线程上完成。当主线程忙时,页面无法响应用户输入。因此,请密切关注下载和执行代码对用户体验的影响。

注意:并非所有Javascript引擎和浏览器当前都实现了脚本流加载优化。但我们仍然相信本文的整体指导将帮助您改善用户体验。

RTLSNam5ZxLDlM

分析JSON的开销

JSON语法比JavaScript语法简单得多,因此JSON比Javascript更有效。基于此,Web应用程序可以提供类似于JSON的大型配置对象文本,而不是将数据内联为Javascript对象文本,这可以极大地提高Web应用程序的加载性能。如下图所示:

Const data={foo: 42,bar: 1337}; //

.它可以表示为JSON字符串,然后在运行时解析JSON。如下图所示:

Const data=JSON.parse('{'foo': 42,'bar': 1337}'); //

只要JSON字符串仅被评估一次,JSON.parse方法比Javascript对象文本快得多,尤其是在冷加载时。

将普通对象文本用于大量数据时会有额外的风险:它们可能会被解析两次!

第一次是文本预解析。

第二次是文本延迟的时间。

第一个解析是必需的,对象文本可以放在顶层或PIFE中以避免第二次解析。

RTRIuPNI7JPB03

重复访问时解析/编译的情况是什么?

V8引擎的(字节)代码缓存优化有助于改善重复访问的体验。首次请求脚本时,Chrome将下载脚本并将其交给V8引擎进行编译。还将文件存储在浏览器的磁盘缓存中。当第二次请求JS文件时,Chrome将从浏览器缓存中检索文件并将其交给V8引擎进行编译。但是,已编译的代码将被序列化并作为元数据附加到缓存的脚本文件。

RVd1nY4Fx3l5C5

V8引擎代码缓存的示意图

第三次请求脚本时,Chrome会从缓存中获取脚本文件和文件的元数据,并将它们传递给V8引擎。 V8引擎将反序列化元数据以跳过编译步骤。如果前两次访问间隔时间少于72小时,则代码缓存将启动。如果使用服务工作程序缓存脚本,则chrome还将主动启动代码缓存。有关详细信息,请参阅Web开发人员的代码缓存指南。

RTRIuPaahjHjS

总结

它是2019年。脚本下载和执行的时间开销已成为加载脚本的主要瓶颈。因此,您应为第一个屏幕内容准备一个较小的同步(内联)脚本包,其余使用一个或多个延迟脚本,并将较大的包拆分为许多小包以按需加载。这将充分利用V8引擎的并行化功能。

在移动设备上,由于网络,内存消耗和CPU执行时间限制,您需要最小化脚本数量,平衡延迟和缓存设置,并允许尽可能在主线程外执行解析和编译。

原文:

本文翻译为CSDN,请注明出处。

[结束]