浏览器性能优化实战

本文探讨了浏览器性能优化的本质,强调了展示速度和响应速度的重要性。详细介绍了浏览器的多进程架构,特别是Renderer和GPU进程在渲染中的作用。文章还讨论了雅虎军规在现代性能优化中的意义,并深入讲解了Core Web Vitals,包括LCP、FID和CLS的定义、测量和优化策略。此外,列举了一系列性能分析工具,如Lighthouse、PageSpeed Insights和Chrome DevTools,强调了正确使用性能工具进行监控和优化的重要性。
摘要由CSDN通过智能技术生成

作者:rosefang,腾讯 PCG 前端开发工程师

当我们在做性能优化的时候,我们究竟在优化什么?浏览器底层是一个什么架构?浏览器渲染的本质究竟是什么?哪些方面对用户的体验影响才是最大的?有没有业内一些通用的标准或标杆参考?都 1202 年了,雅虎军规还有没有用?性能分析工具都有哪些?我们怎么进行打点分析才是合适的?

本文为你一一讲解这些。了解了这些问题,可能你在做性能优化的时候才能更加得心应手。

1. 性能优化的本质

1.1 展示更快,响应更快

性能优化的目的,就是为了提供给用户更好的体验,这些体验包含这几个方面:展示更快、交互响应快、页面无卡顿情况。

更详细的说,就是指,在用户输入 url 到站点完整把整个页面展示出来的过程中,通过各种优化策略和方法,让页面加载更快;在用户使用过程中,让用户的操作响应更及时,有更好的用户体验。

对于前端工程师来说,要做好性能优化,需要理解浏览器加载和渲染的本质。理解了本质原理,才能更好的去做优化。所以我们先来看看浏览器架构是怎样的。

1.2 理解浏览器多进程架构

从大的方面来说,浏览器是一个多进程架构。

它可以是一个进程包含多个线程,也可以是多个进程中,每个进程有多个线程,线程之间通过 IPC 通讯。每个浏览器有不同的实现细节,并没有标准规定浏览器必须如何去实现。

这里我们只谈论 chrome 架构。

下面这张图是目前 chrome 的多进程架构图。

图片引自 Mariko Kosaka 的《Inside look at modern web browser》

我们来看看这些进程分别对应浏览器窗口中的哪一部分:

图片引自 Mariko Kosaka 的《Inside look at modern web browser》

那么,怎么看浏览器对应启动了什么进程呢?

chrome 中,我们可以通过更多->More Tools->Task Manager 看到启动的进程。

从 chrome 官网和源码,我们也可以得知,多进程架构中包含这些进程:

  • Browser 进程:打开浏览器后,始终只有一个。该进程有 UI 线程、Network 线程、Storage 线程等。用户输入 url 后,首先是 Browser 进程进行响应和请求服务器获取数据。然后传递给 Renderer 进程。

  • Renderer 进程:每一个 tab 一个,负责 html、css、js 执行的整个过程。前端性能优化也与这个进程有关

  • Plugin 进程:与浏览器插件相关,例如 flash 等。

  • GPU 进程:浏览器共用一个。主要负责把 Renderer 进程中绘制好的 tile 位图作为纹理上传到 GPU,并调用 GPU 相关方法把纹理 draw 到屏幕上。

这里的话只是简单介绍一下浏览器的多进程架构,让大家对浏览器整体架构有个初步认识,其实背后的细节还有很多,这里就不一一展开。有兴趣可以细看这一系列文章chrome 官网介绍。

1.3 理解页面渲染相关进程
1.3.1 Renderer Process & GPU Process

从以上的多架构,我们了解到,与前端渲染、性能优化相关的,其实主要是 Renderer 进程和 GPU 进程。那么,它们又是什么架构呢?

来看一下这张我们再熟悉不过的图。

图片引自 Paul 的 《The Anatomy of a Frame》
  • Renderer 进程:包括 3 个线程。合成线程(Compositor Thread)、主线程(Main Thread)、Compositor Tile Worker。

  • GPU 进程:只有 GPU 线程,负责接收从 Renderer 进程中的 Compositor Thread 传过来的纹理,显示到屏幕上。

1.3.2 Renderer Process 详解

Renderer 进程中 3 个线程的作用为:

  • Compositor Thread:首先接收 vsync 信号(vsync 信号是指操作系统指示浏览器去绘制新的帧),任何事件都会先到达 Compositor 线程。如果主线程没有绑定事件,那么 Compositor 线程将避免进入主线程,并尝试将输入转换为屏幕上的移动。它将更新的图层位置信息作为帧通过 GPU 线程传递给 GPU 进行绘制。

当用户在快速滑动过程中,如果主线程没有绑定事件,Compositor 线程是可以快速响应并绘制的,这是浏览器做的一个优化。

  • Main Thread:主线程就是我们前端工程师熟知的线程,这里会执行解析 Html、样式计算、布局、绘制、合成等动作。所以关于性能的问题,都发生在了这里。所以应该重点关注这里

  • Compositor Tile Worker:由合成线程产生一个或多个 worker 来处理光栅化的工作。

Service Workers 和 Web Workers 可以暂时理解也在 Renderer 进程中,这里不展开讨论。

1.3.2.1 Main Thread
main-thread

主线程需要重点讲下。因为这是我们的代码真实存在的环境。

从上一小节 Render 进程和 GPU 进程的图中,我们可以看到有个红色的箭头,从 Recal Styles 和 Layout 指向了 requestAnimationFrame,这意味着有 Forced Synchronous Layout (or Styles)(强制回流和重绘)发生,这一点在性能方面特别要注意。

在 Main Thread 中,有这几个需要注意一下:

  • requestAnimationFrame:因为布局和样式计算是在 rAF 之后,所以在 rAF 是进行元素变更的理想时机。如果在这里对一个元素变更 100 个类,不会进行 100 次计算,它们会分批以后处理。需要注意的是,不能在 rAF 中查询任何计算样式和布局的属性(例如:el.style.backgroundImage 或 el.style.offsetWidth),因为这样会导致重绘和回流。

  • Layout:布局的计算通常是针对整个文档的,并且与 DOM 元素的大小成正比!(这点特别要注意,如果一个页面 DOM 元素太多,也会导致性能问题)

主线程的顺序始终都是:

Input Event Handler->requestAnimationFrame->ParseHtml->ReculateStyles->Layout- >Update Layer Tree->Paint->Composite->commit->requestIdleCallback

只能从前往后,例如,必须先是 ReculateStyles,然后 Layout、然后 Paint。但是,如果它只需要做最后一步 Paint,那么这就是它全部要做的事情,不会再发生前面的 ReculateStyles 和 Layout。

这里其实给了我们一个启示:如果要让 fps 保持 60,即每帧的 js 执行时间少于 16.66ms,那么让这个主线程执行的过程尽可能地少,是我们的性能优化目标

根据主线程的这些步骤,理想的情况下,我们只希望浏览器只发生最后一个步骤:Composite(合成)。

CSS 的属性是我们需要关注一下的模块。这里有描述了哪些CSS 属性会引起重绘、回流和合成。例如,让我们给一个元素进行移动位置时:transformopacity可以直接触发合成,但是lefttop却会触发 Layout、Paint、Composite3 个动作。所以显然用 transform 时更好的方案。

但这并不是说我们不应该用 left 和 top 这些可能引起重绘回流的属性,而是应该关注每个属性在浏览器性能中引起的效果

2. 看看经典:雅虎军规

多年前雅虎的 Nicolas C. Zakas 提出 7 个类别 35 条军规,至今为止很多前端优化准则都是围绕着这个展开。如果严格按照这些规则去做,其实我们有很多优化工作可以做,只要认真践行,性能提升不是问题。

我们来看看它 7 个分类都是围绕哪些方面展开:

  • Server:与页面发起请求的相关;

  • Cookie:与页面发起请求相关;

  • Mobile:与页面请求相关;

  • Content:与页面渲染相关;

  • Image:与页面渲染相关;

  • CSS:与页面渲染相关;

  • Javascript:与页面渲染和交互相关。

从上面的描述可以看到,其实雅虎军规,是围绕页面发起请求那一刻,到页面渲染完成,页面开始交互这几个方面来展开,提出的一些原则。

很多原则大家也都耳熟能详,就不全部展开了,有兴趣的同学可以去查看原文。这里主要想提一些忽略但是又值得注意的点:

减少 DOM 节点数量

为什么要减少 DOM 节点的数量?

当遍历查询 500 和 5000 个 DOM 节点,进行事件绑定时,会有所差别。

当一个页面 DOM 节点过多,应该考虑使用无限滚动方案来使视窗节点可控。可以看看google 提的方案

减少 cookie 大小

cookie 传输会造成带宽浪费,影响响应时间,可以这样做:

消除不必要的 cookies;

静态资源不需要 cookie,可以采用其他的域名,不会主动带上 cookie。

避免图片 src 为空

图片 src 为空时,不同浏览器会有不同的副作用,会重新发起一起请求。

3. 性能指标

3.1 什么样的性能指标才能真正代表用户体验?

要衡量性能,我们必须有一些客观的、可衡量的指标来进行监控。但是客观且定量可衡量的指标不一定能反映用户的真实体验

以前,我们会用 load 事件的触发来衡量一个页面是否加载或显示完成。但是设想会不会有这样的情况:一个页面的 load 事件已经被触发,但是却在 load 事件之后几秒才开始加载内容和渲染页面,所以这个时候,load 事件并不能真实反映用户看到内容的时刻。

在过去几年,google 团队和W3C 性能工作组致力于提供标准的性能 API 来真正衡量用户的体验。主要是从这 4 个方面思考:

思考点 详细内容
Is it happening? 导航是否成功,服务器是否响应了
Is it useful? 是否已经渲染了足够的内容,让用户可以开始参与其中
Is it usable? 用户是否可以与页面交互,页面是否处于繁忙状态
Is it delightful? 交互是否流畅、自然、没有滞后反映或卡顿

通常有 2 种途径来衡量性能。

  1. 本地实验衡量:本地模拟用户的网络、设备等情况进行测试。通常在开发新功能的时候,实验测量是很重要的,因为我们不知道这个功能发布到线上会有什么性能问题,所以提前进行性能测试,可以进行预防。

  2. 线上衡量:实验测量固然可以反映一些问题,但无法反映在用户那里真实的情况。同样的,在用户那里,性能问题会和用户的设备、网络情况有关,而且还跟用户如何与页面进行交互有关。

有这几个类型与用户感知性能相关。

  • 页面加载时间:页面以多快的速度加载和渲染元素到页面上。

  • 加载后响应时间:页面加载和执行 js 代码后多久能响应用户交互。

  • 运行时响应:页面加载完成后,对用户的交互响应时

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值