思维花园

性能优化的原理及分析

如何在前端开展一次性能优化工作

未完待续

性能优化是大家熟的不能再熟的概念、命题、目标。更多的大家只关注How(how很重要),而不太关注Why(为什么要做,为什么这个方案work或者不work), 也不太关注When(何时做以及要不要做)。而我始终认为性能优化是一个系统工程,和其它工程一样是有一套底层的理论体系做支撑的。今天我尝试做一个总结,权当抛转。

在开始之前我抛一些问题:

  • 为什么网页二次打开会很快?
  • 为什么手淘跟统一接入之间要维持长链接?
  • 为什么说接口包大小会影响响应速度?
  • 为什么绝大部分情况下SSR比CSR快?
  • 为什么机房要做单元化改造?
  • 监控系统 上哪些指标都啥意思 ?
  • 监控系统上指标都是怎么来的 ?
  • 如何正确的开始一次性能优化?
  • 等等...

1. 从一道经典面试题开始

这道题目就是「从浏览器输入网址按回车开始到网页展示发生了什么?」。 这个题目可以一句话说完,也可以「罄竹难书」,今天我们把主要目光关注在和性能相关的过程上。 首先这是发生在B/S 架构下的一个问题。 从抽象的角度看,上面已经是非常抽象的存在,对于理解架构有利,但是性能优化是一件需要「抽丝剥茧」的事情。这个架构还是需要进一步细化。但大致可以分位三大块:

  • 浏览器及运行在浏览器里的应用程序(这里特指前端的页面)
  • 网络及运行在网络内的应用程序
  • 运行在服务端的设备、服务和程序

1.1 先说说这个面试题

B/S 架构的特点是:请求-响应。理解这一点很关键,后面会说。

如无特殊说明这里特指chrome为代表的的浏览器

预备知识:

  • 当代浏览器都是多进程模型,在chrome浏览器下一般会有4个进程
  • 浏览器主进程:负责页面展示,用户交互,子进程管理等功能
  • 渲染进程:每个选项卡都有自己的渲染进程,无关乎是否为 same-site 站点,SandBox 运行环境,处理 HTML、CSS、JavaScript。同时 V8 和 Blink 也都运行在该进程中。
  • 插件进程:负责插件运行,根据插件的功能决定是否运行在 Sandbox 环境
  • GPU 进程:处理一些特殊的 CSS 效果
  • NetWork Service:处理网络资源加载,请求响应,校验 CORS。
  • Storage Service:处理对 localStorage、sessionStorage、cookie、Indexed DB 存储的控制。
  • Audio Service:处理音视频 Buffer 的音量播放等操作
  • RTT: **round-trip time **

RTT(Round-Trip Time): 往返时延。在计算机网络中它是一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。 一般认为单向时延=传输时延t1+传播时延t2+排队时延t3 t1是数据从进入节点到传输媒体所需要的时间,通常等于数据块长度/信道带宽 t2是信号在信道中需要传播一定距离而花费的时间,等于信道长度/传播速率(光纤中电磁波的传播速率约为210^5 km/s,铜缆中2.310^5 km/s) t3可笼统归纳为随机噪声,由途径的每一跳设备及收发两端负荷情况及吞吐排队情况决定(包含互联网设备和传输设备时延) 综上:时延并无标准值只有经验值。某运营商规定网内路由器间时延1000公里之内<=40ms,2000公里之内<=60ms,3000公里之内<=80ms

按完回车后,浏览器内主要发生以下几步:

  1. 检验输入,是搜索词跳搜索,是链接或者域名继续
    1. 这里有执行beforeunload的时机(你经常遇到的“你确定要退出吗”,就发生在这里。
  2. 浏览器是否有缓存,有直接返回,否则继续
    1. NetWork Service 会委托 Storage Service 依次在 service worker cache、memory cache、disk cache、push cache(HTTP2 Stream)中寻找对应的 URI 是否有可用的强缓存,如果存在强缓存,则直接使用缓存进入浏览器解析环节,否则进入 DNS 解析.
    2. 这里面给我们有几个启示:
      1. service worker 应用
      2. http cache 应用
      3. pre-fetch 应用等
  3. DNS解析(这是一个知识点,也是一个优化点我们后面讲)
    1. DNS 的概念及分类
    2. CDN 与DNS的关系
    3. 为什么有HTTPDNS
    4. DNS怎么影响了性能
    5. 怎么优化
  4. 排队:TCP队列(也就是传说中浏览器针对单个域名有6个并发请求限制)
  5. 发送网络请求:,
    1. TCP 建立链接 :三次握手、慢启动、流量控制、队首拥塞、(知识点,也是网络优化的重点)
    2. 如果是https 协议,建立TLS链接
      1. 最多需要两次握手
    3. 构建HTTP请求信息(Request Header + Request Line ),发送请求
      1. 请求行: 方法(比如GET,POST) + PATH (比如/index.html ) + 协议版本这个版本很重要,也是我们性能优化的重点,后面会讲
        1. http 1.0 协议
        2. http 1.1 协议
        3. http 2.0 协议
        4. http 3.0 协议
      2. 请求header (包含该域名下的cookie等信息)
    4. 中间数据包的传输过程
      1. 操作系统,驱动程序,网卡,光电信息号
      2. WIFI 、蜂窝网络、有线网(属于用户端网络的最后一公里范畴,延迟的重灾区)
      3. 核心网、骨干网、ISP
      4. 数据中心及其内部网络
      5. 本部分的主要问题其实是TCP协议本身的限制导致的网络延迟问题(敦促了http 3.0 协议的升级)
        1. 3次握手
        2. 拥塞窗口
        3. 队首拥塞
        4. 慢启动
        5. 丢包
    5. 服务端接受请求后,解析请求信息,内部运算后产生响应数据(Response Header, Response status, Body) , 发送响应请求(服务端这一块,后面我会讲,是很大的一块)(从分工上来讲,这块需要服务端去优化)。
      1. 服务端其实是一个非常抽象的概念
      2. 服务端既是服务端也是客户端 ,尤其是当前微服务架构模式占比比较多的当下
      3. 服务端既是单个数据中心,也可以跨(地域)数据中心 (为什么公司要做单元化改造?)
      4. 集群
        1. DB
        2. 缓存
        3. 队列
        4. 配置中心
        5. 负载均衡
      5. 单机
        1. 程序本身(java、node、c、c++等)
        2. 操作系统
        3. 硬件
          1. 网卡
          2. CPU
          3. 内存
          4. 存储类型
  6. 浏览器网络进程解析请求返回头及返回行(这里面主要看状态码,以及Content-Type)(TTFB )
    1. 状态码的知识大家应该都比较清楚
    2. content-type 需要细说下,比如浏览器怎么知道什么时候渲染html, 什么时候是调用下载,什么时候调用播放器播放视频呢,就是这个东西,类型很多,常见的 (text/html、application/json等)
    3. 如果这里content-type 不是类似 application/octet-stream 这种下载类型(调用下载器,流程结束),比如我们这里指html这种类型,则会继续通知主进程 准备渲染进程进一步进行渲染
    4. 如果这个时候遇到重定向则会重新执行(DNS解析-> 发起请求的过程)(现实中的例子:微信里面商品详情分享去掉一次302跳转,优化100ms+
  7. 创建渲染进程并从网络进程中读取响应体数据 开始解析过程 (这里面的知识点是,现代浏览器都是基于流式渲染的,并不是需要完整的下载完才开始(这里可以参见关于使用ER做流式渲染的文章))
    1. 遇到css文件会跳过(阻塞渲染,但不阻塞解析)
    2. 遇到没有添加async 或者defer属性的script 则会阻碍解析
    3. 解析完成后就形成了DOM 树
    4. 处理CSS构建CSSOM树
      1. 遇到 link 的css 要下载来、style 标签里面的,inline 的这些 转换成浏览器认识的数据结构- styleSheets.
      2. 属性值标准化(比如em 转成px,blod 转成700)
      3. 计算DOM树中每个节点的样式(请问啥叫css的继承与层叠哇?)
        1. 继承就是从父元素等继承下来的
        2. 层叠就是多个来源都作用于一个元素的时候,优先级咋算,也就是到底谁生效
        3. 最后生成Computed Style
  1. JavaScript 编译 (这一层优化可以说比较底层了)
    1. 源代码 到 AST
    2. AST 到字节码
    3. 字节码到机器码
  2. 构建辅助功能树(无障碍相关)
  3. 合并 DOM 树 和CSSOM 到render 树 (忽略了那些隐藏的元素
  4. 渲染(前端同学需要开始关注了)
    1. 样式计算
      1. 遇到 link 的css 要下载来(阻塞的哦)、style 标签里面的,inline 的这些 转换成浏览器认识的数据结构- styleSheets.
      2. 属性值标准化(比如em 转成px,blod 转成700)
      3. 计算DOM树中每个节点的样式(请问啥叫css的继承与层叠哇?)
        1. 继承就是从父元素等继承下来的
        2. 层叠就是多个来源都作用于一个元素的时候,优先级咋算,也就是到底谁生效
      4. 最后生成Computed Style
  1. 布局阶段(Layout) (计算DOM树中可见元素(宽度,高度,位置)的过程)
    1. 创建(可见元素)布局树
  1. 布局计算 (计算节点的坐标位置)
    1. 这里面一个知识点: **第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流
  2. 分层阶段
    1. 一些3D, transform, z-index 过的这些节点生成专用的图层,并生成图层树(LayerTree)
    2. 什么情况下会被提升成一个单独的层呢 (下列二选一)?
      1. 具有层叠上下文**的元素 **
      2. 因为超过元素容器大小(比如overflow: hidden, auto 后)被截断,出现滚动条
    3. 分层确实可以提升性能,一般会到GPU中加速
  3. 图层绘制: 生成记录绘制顺序和绘制指令的列表
  4. 栅格化(raster)操作
    1. 合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图
    2. 一般这一步操作在GPU中进行,也叫做快速栅格化
  5. 合成与显示 (将上一步光栅化的内容存到内存,显示到显示器的过程)
    1. 这里的知识点是,首次绘制的节点:FMP( first meaningful paint) (不过这个被FCPFirst Contentful Paint(en-US)替代了)
    2. 随着 不同层的绘制,会相互重叠,则必须进行合成。
  1. 交互

一旦主线程绘制页面完成,你会认为我们已经“准备好了”,但事实并非如此。如果加载包含 JavaScript(并且延迟到 onload 事件激发后执行),则主线程可能很忙,无法用于滚动、触摸和其他交互。 Time to Interactive(en-US)(TTI)是测量从第一个请求导致 DNS 查询和 SSL 连接到页面可交互时所用的时间——可交互是 First Contentful Paint(en-US) 之后的时间点,页面在 50ms 内响应用户的交互。如果主线程正在解析、编译和执行 JavaScript,则它不可用,因此无法及时(小于 50ms)响应用户交互。

1.2 当代浏览器及前端页面

1.3 网络及协议

1.4 现代后端服务设计

2. 性能优化的数理基础

本质是为了如何度量

2.1 概率与统计

2.2 支撑法则

2.3 如何AB

3. 如何开启一次正确性能优化之旅

知道why后,how也很重要,很多同学感兴趣的可能是这一部分 ^_^

3.1 甄别体验问题

3.2 值不值得(现在)做

3.3 梳理关键渲染路径及耗时

关键渲染路径 - Web 性能 | MDN

3.4 总结的一些经验法则

3.5 常用工具介绍

3.5.1 chrome 网络面板介绍

3.5.2 监控系统 性能介绍

3.5.3 lightHouse

4. 防腐-性能优化是一个渐进的系统工程