近年来,
instagram.com上出现了许多新
事物 。 很多 例如-讲故事的工具,过滤器,创意工具,通知,直接消息。 然而,随着项目的发展,这一切给了悲伤的副作用,那就是instagram.com的性能开始下降。 在过去的一年中,Instagram开发团队一直在努力解决此问题。 这导致了一个事实,Instagram提要(提要页面)的总加载时间减少了近50%。

今天,我们发布了一系列文章的第一篇材料的翻译,这些文章专门介绍instagram.com是如何加速的。
关于优化Web项目的性能
过去一年的效果改善(Instagram feed,Display Done指标,毫秒)改善Web应用程序性能的最重要方法之一是适当地确定加载和处理资源的优先级,并减少页面加载期间的浏览器停机时间。 在我们的案例中,许多优化措施已被证明比减少代码大小更有效。 通常,我们对代码的大小没有任何抱怨。 它足够紧凑。 仅在对该项目进行了许多小改进之后(我们还计划讲述代码大小优化),它的尺寸才开始困扰我们。 此外,此类改进对项目开发过程的影响较小。 他们需要更少的代码更改和更少的重构。 因此,我们首先从预加载资源开始就将精力集中在这一领域。
有关预加载图像,JavaScript代码和完成查询所需材料以及需要注意的地方的故事
我们优化的一般原则是尽快告知浏览器加载页面所需的资源。 作为项目开发人员,我们在很多情况下都事先知道确切需要什么。 但是浏览器可能对此一无所知,直到页面材料的某些部分被加载和处理。 所讨论的资源大部分包括使用JavaScript动态加载的资源(例如,其他脚本,图像,执行XHR请求所需的材料)。 事实是,浏览器在解析并执行一些JavaScript代码之前无法检测到这些依赖资源。
与其等到浏览器本身找到这些资源,不如给我们一个提示,然后它可以立即开始下载它们。 我们使用
preload
HTML属性进行了此操作。 看起来像这样:
<link rel="preload" href="my-js-file.js" as="script" type="text/javascript" />
对于关键页面加载路径上的两种资源,我们使用类似的提示。 这是动态加载的JavaScript代码和GraphQL XHR请求数据的动态加载材料。 动态加载的脚本是使用特定客户端路由的
import('...')
形式的结构加载的脚本。 我们维护服务器入口点和客户端路由脚本的对应列表。 结果,当我们在服务器上收到加载页面的请求时,我们就知道需要下载客户端路由的脚本。 结果,我们可以在生成页面的HTML代码时向其添加适当的提示。
例如,当使用
FeedPage
入口点时
FeedPage
我们知道客户端路由器最终将完成下载
FeedPageContainer.js
的请求。 结果,我们可以将以下构造添加到页面代码中:
<link rel="preload" href="/static/FeedPageContainer.js" as="script" type="text/javascript" />
同样,如果我们知道计划为特定页面的入口点执行GraphQL查询,则这意味着我们需要预加载材料以加快此查询的执行。 由于执行此类GraphQL查询有时会花费大量时间,并且在返回查询结果之前页面无法呈现,因此这一点尤其重要。 因此,我们需要使服务器尽早参与对此类请求的响应。
<link rel="preload" href="/graphql/query?id=12345" as="fetch" type="application/json" />
页面加载功能的变化在连接速度慢的情况下尤其明显。 通过模拟快速3G连接(下面的第一个瀑布图,该图说明了不使用资源预加载的情况),我们可以看到加载
FeedPageContainer.js
并执行与其关联的GraphQL查询仅在加载
Consumer.js
之后才开始。 但是,在使用预加载的情况下,可以在HTML页面可用后立即开始加载
FeedPageContainer.js
脚本并执行GraphQL查询。 此外,这还减少了下载使用延迟加载机制的所有次要脚本所需的时间。 在这里,
FeedSidebarContainer.js
和
ActivityFeedBox.js
(取决于
FeedPageContainer.js
)在处理
Consumer.js
之后几乎立即开始加载。
未使用预加载预加载使用优先预载的好处
除了使用
preload
属性更快地开始加载资源外,使用此机制还有另一个优点。 它在于提高异步脚本加载的网络优先级。 当在关键路径中使用异步加载的脚本加载页面时,这很重要,因为默认情况下,它们以低优先级加载。 结果,XHR请求和与用户可见的页面区域相关的图像的优先级将高于查看区域之外的材料。 但这可能导致渲染页面所需的关键脚本被阻止或被迫与其他资源共享带宽。 如果您有兴趣,
这里是有关Chrome资源优先级
的详细说明。 精心使用预加载机制(我们将在下文中详细介绍)使开发人员可以一定程度地控制浏览器如何确定初始加载页面的优先级。 当开发人员知道哪些资源对于正确显示页面很重要时,尤其如此。
预加载优先级问题
预加载资源的问题恰恰在于它为开发人员提供了更多影响资源加载优先级的优势。 这意味着开发人员对适当的优先级负责。 例如,当在移动网络和WiFi网络的速度非常低并且发现大量数据包丢失的区域中测试站点时,我们注意到在处理
<link rel="preload" as="script">
与处理关键页面呈现路径中使用
<script />
JavaScript捆绑包的
<script />
时,执行的请求相比,
<link rel="preload" as="script">
具有更高的优先级。 这导致整个页面加载时间增加。
问题的根源是我们如何在页面上放置预加载标签。 也就是说,我们仅为捆绑包添加了预加载提示,这些包是当前页面的一部分,我们将使用客户端路由器异步加载它们。
<link rel="preload" href="SomeConsumerRoute.js" as="script" /> <link rel="preload" href="..." as="script" /> ... <script src="Common.js" type="text/javascript"></script> <script src="Consumer.js" type="text/javascript"></script>
例如,在注销页面上,我们将
SomeConsumerRoute.js
加载到
Common.js
和
Consumer.js
,并且由于预加载资源以更高的优先级加载,但没有被解析,因此这会阻止
Common.js
和
Consumer.js
解析
Consumer.js
。 Chrome Data Saver开发团队发现了类似的预加载问题,并
描述了他们针对此问题的解决方案。 在他们的情况下,决定始终将用于预加载异步资源的结构放在使用这些异步资源的那些资源的
<script />
标记之后。 我们决定预加载所有脚本,并将相应的结构按需要的顺序放置在代码中。 这使我们有机会尽快开始预加载页面的所有脚本资源。 这包括用于同步加载脚本的标签,这些脚本在页面上放置特定服务器数据之前无法添加到HTML。 这使我们可以控制脚本的加载顺序。
这是预加载所有JavaScript包的标记。
<link rel="preload" href="Common.js" as="script" /> <link rel="preload" href="Consumer.js" as="script" /> <link rel="preload" href="SomeConsumerRoute.js" as="script" /> ... <script src="Common.js" type="text/javascript"></script> <script src="Consumer.js" type="text/javascript"></script> <script src="SomeConsumerRoute.js" type="text/javascript" async></script>
图像预加载
Feed是instagram.com的主要工作领域之一。 它是一个图像和视频页面,支持无限滚动。 我们这样填充此页面。 首先,下载初始的出版物集,然后,当用户滚动页面时,加载其他材料集。 但是,我们不希望用户每次到达磁带底部时都等待加载新材料。 因此,为了方便使用此页面,我们在用户到达磁带末端之前上载了新的材料集。
实际上,出于多种原因,这并非易事:
- 我们需要下载用户不可见的资料,以便它们不会从他正在查看的资料中获取网络和处理器资源。
- 我们不希望通过网络传输不必要的数据,而是尝试过分预加载用户可能看不到的出版物。 但是,另一方面,如果我们没有预装足够数量的材料,这通常意味着用户会“碰上”磁带末端的风险。
- instagram.com项目旨在在各种设备和各种尺寸的屏幕上工作。 结果,我们使用
<img>
的srcset
属性在磁带中显示图像。 在给定屏幕尺寸的情况下,此属性允许浏览器确定要使用的图像分辨率。 这意味着我们要预先确定需要下载的图像的分辨率并不容易。 另外,存在预加载浏览器将不使用的图像的风险。
我们用于解决此问题的方法是创建优先任务的抽象,该任务负责对异步任务进行排队(在这种情况下,这些任务是用于预载下一组出版物以在磁带中输出的任务)。 最初,类似的任务以优先级为
idle
队列(在这里使用
requestIdleCallback
)。 这意味着只要浏览器忙于其他重要工作,就不会开始执行此类任务。 但是,如果用户将页面滚动到足够接近当前下载出版物集的结束位置的位置,则此预加载材料任务的优先级将变为
high
。 这是通过取消备用回调来完成的,此后立即开始预加载过程。
在磁带的开头和中间,数据预加载任务的优先级为空闲,而在磁带的结尾,优先级为高下一批出版物的JSON数据下载完成后,我们将排队执行重复的后台任务,以从该批次中预加载图像。 图像预加载是按照在Feed中显示出版物的顺序而不是并行进行的。 这使我们能够确定数据加载任务的优先级,并为最接近用户所看到页面位置的出版物显示图像。 要下载正确大小的图像,我们使用隐藏的媒体组件,其参数与当前磁带的参数相对应。 在此组件内部,有一个
<img>
元素,该元素使用
srcset
属性,该元素用于在feed中显示实际的发布。 这意味着我们可以为浏览器提供决定要预加载哪些图像的能力。 结果,浏览器在显示图像时将使用与预加载图像时相同的逻辑。 这也意味着我们使用类似的媒体组件可以为网站的其他区域预加载图像。 例如用户个人资料页面。
上述改进的整体效果使上传照片所需的时间减少了25%。 我们正在谈论的是从发布代码添加到DOM到加载和显示发布的图像之间的时间长度。 此外,这使Feed结束时用户花在等待下载新资料上的时间减少了56%。
亲爱的读者们! 您是否使用数据预加载机制来优化Web项目?
