《前端技术架构与工程》之性能
前言:
《前端技术架构与工程》这本书真的越看越有味。目前写了部分这本书的笔记,共分为三部分做笔记,已写了两篇如下。
《前端技术架构与工程》初次笔记
《前端技术架构与工程》之编程语言
今天准备写技术架构部分(编程语言、技术规范、组件化、前后端分离、性能)中的性能部分。
初次笔记让我从架构师的角度认识前端技术栈,编程语言笔记让我对前端三大件有了新的理解。
虽然技术规范、组件化、前后端分离里面的知识也重要,但是我这三部分都要了初步了解,勉强够用。唯一性能这一块没有去深入了解,而性能这一块又挺重要的。于是今天特意先做一下性能这一块的笔记。结合《前端技术架构与工程》的第六章以及前端学习梳理 的性能优化部分进行展开。
我的笔记只是二手资料,详情请自行找资源。
文章目录
- 《前端技术架构与工程》之性能
- 前言:
- 性能
- 性能评估模型
- 加载阶段
- 可交互阶段
- 业务性能
- 从URL到图形
- 网络
- 渲染
- 内存管理
- GC算法
- 内存泄漏
- 内存泄漏的处理方法
- 避免全局变量。
- 谨慎处理闭包。
- 使用编译工具。
- 极限运算性能
- web worker与并行计算
- webAssembly
- WebGPU
- 题外话
- 《高性能网站建设指南》的借鉴
性能
性能是衡量软件架构最基本也是最核心的直播之一。在前端领域,HTML5新增的web worker可实现多线程和并行计算以提高运算性能;CSS3的transform 3D借助GPU加速提高动画流畅度;Node.js得益于V8引擎优异的性能表现而普及。互联网产品,尤其是TOC产品,性能是影响用户体验的核心因素之一。了解性能的优化是有必要的。
在以用户为中心的互联网时代,优秀的用户体验的web应用抢占市场的重要武器,而性能的决定用户体验的核心。加载速度快能够给用户良好的第一印象,流畅的交互是支持功能最具象的要素。
- 在加载性能上,网络架构层是优化宗旨是减少延迟以提高数据获取的速度,异步script能够减少JavaScript代码对渲染的阻塞从而令HTML文档尽快渲染;
- 在应用执行期间,熟知浏览器GC策略有助于编写对内存友好的代码,可避免因内存泄漏导致的应用程序交互卡顿甚至死机的现象。
在《前端技术架构与工程》的性能部分主要从性能评估模型、从URL到图形、内存管理、极限运算性能这四部分进行讲述。
- 在性能评估模型中:讲性能指标;
- 从URL到图形:讲如何在加载阶段和可交互阶段优化性能,方法是获取数据和渲染HTML文档;
- 内存管理:讲在浏览器有限的内存配额下如何优化代码以防止内存泄漏;
- 极限运算性能:将在大数据处理的项目如何发挥浏览器的极限运算性能;
性能评估模型
制定性能评估模板最基本的原则是:对客户端场景参数(设备、浏览器、网络)赋予固定的值,使得应用限定在一致的客户端场景中,然后再进行对比。
web应用程序的生命周期分两个阶段:一是加载阶段(从输入URL到显示网页的阶段),二是可交互阶段。
性能评估(贯穿这本书的基本原则,技术服务于业务)分为:加载性能、动态性能、业务性能;
加载阶段
加载阶段的性能被称为加载性能。优化加载性能可细分为两个方向:一是从视觉角度提高网站内容的渲染速度,对应白屏时间和首屏时间两项指标;二是从交互角度缩短打开网站到可交互之间的时间间隔,对应可交互节点指标;在这本书第二章将预渲染的时候就有白屏的优化方案。
可交互阶段
加载结束,网站进入可交互阶段,由于可交互是动态的,故此阶段的性能定义为动态性能。优化动态性能也有两个方向:一是反馈速度,如新加载内容的加载;二是动画帧率,不过这个不做深入了解,目前占个坑;
业务性能
业务性能单独挑出来讲,因为技术服务于商业,商业的优先级远高于技术。在加载阶段,业务性能指标有两个:首次有效绘制和广告可视节点;在可交互阶段,业务性能指标有关键路径渲染。
从URL到图形
谈到网页性能优化就不可避免需要了解从URL到页面的具体过程。这个过程不仅和加载相关也和交互相关。学习前端不仅需要学习HTML、CSS、JavaScript,还需要对浏览器的实现过程熟悉,虽然它不能直接提高代码的质量和开发的效率,但是能为方案设计提高坚实的理论基础。
浏览器的大致架构分为三层:操作系统层、内核层、应用层;
- 应用层:包含一些可视化的交互模块,如书签管理、窗口管理等;以及一些不可见的数据管理模块,如历史记录等;
- 操作系统层:提供浏览器所需的系统API,如多线程、文件I/O等;
- 内核层:浏览器的核心,包括两个部分,一是渲染引擎,包括HTML、CSS、SVG的解释器和JavaScript引擎,以及布局、绘制相关的模块;二是相对底层的功能模块集合,如网络模块、图形库、存储、多媒体解码器;
其中内核层是优化web应用性能的主要突破点,无论加载性能还是动态性能,其中的关键点就在于网络方面、渲染方面、运算方面;
浏览器打开URL的完整流程依次是:当前文档卸载、重定向处理、缓存判断、DNS查询、建立TCP连接、HTTP请求/响应处理、HTML文档解析。在这过程中,截止至开始渲染前,浏览器的所有操作实质上就是尝试获取URL对应的信息,这一部分可以定义为Fetch阶段;接收到HTML文档的HTTP响应后,浏览器开始解析和渲染工作,这一阶段可以定义为Render阶段。在Fetch阶段的时间消耗主要取决于网络环境,不受前端代码的影响;在Render阶段的时间消耗受网络环境和前端逻辑代码的双重影响。
网络
Fetch阶段。流程为当前文档卸载、重定向处理、缓存判断、DNS查询、建立TCP连接、HTTP请求/响应处理。
- 当前文档卸载:如果当前为空白页则无此操作;
- 重定向处理:浏览器在处理重定向上的时间消耗非常大,现实中应当尽量避免。
- 缓存判断:fetch start。如果有缓存就直接返回缓存数据,没有就继续请求。
- DNS:DNS server。绝大多数浏览器都有DNS缓存管理功能,可以节省一部分时间。
- TCP、HTTP请求/响应:web server。获得域名对应的IP后,浏览器便尝试与Web服务器建立TCP连接,成功后随即发生HTTP请求,收到响应数据后便进入Render阶段。
在上述流程中不难看出,影响耗时的几个重要因素。
- 缓存,应用缓存和DNS缓存;
- DNS查询耗时。DNS查询请求优先使用UDP协议,时间消耗非常小;
- 建立TCP连接的三次握手和慢启动好事。TCP慢启动是一种为了防止阻塞崩溃的安全机制,但是很耗时。
- 浏览器发出HTTP请求和服务器处理响应数据的耗时。
- 浏览器下载HTTP响应数据的耗时。这一项取决于网络带宽和URL对应资源的大小。
为了优化这些耗时。有两个思路。一个是减少RTT的总数量。一个是缩短RTT的时长。减少RTT的总数量的方法有持久连接、并行请求、HTTP combo;缩短RTT的时长的方法是CDN技术。
此外还有HTTP2.0,但是难以普及,一方面浏览器的兼容性不理想,另一方面是服务器迁移成本太高。所以针对网络的优化策略仍然面向HTTP1.1。其优化策略是
- 整体架构上:使用持久连接、使用CDN、控制域名数目、无法合并的小体积文件使用HTTP combo;
- 前端上:压缩文件体积、合并小体积文件(使用字体图标)、合理使用缓存、按需加载、避免不必要的下载。
渲染
浏览器在渲染过程在有三种基础数据,及定义网页结构的HTML、描述视觉样式的CSS和承担交互行为的JavaScript,每种数据类型均有对应的解释模块。
HTML解释器将HTML文档解析为DOM树。CSS解释器将CSS解析为CSSOM,渲染引擎按照CSS选择器规则将DOM与CSSOM关联之后对每个DOM应用样式和布局,最终绘制为可视的UI。
浏览器在渲染HTML文档期间,每逢遇到非异步的scrip标签则暂停文档后续内容的解析和渲染,待JavaScript文件加载和执行完后才恢复解析。之所以对scrip标签应用阻塞式的解析策略,是英文JavaScript拥有改变HTML文档结构的权限,它会改变DOM树的具体形态,进而影响最终的视觉效果。
内存管理
JavaScript是一种拥有GC机制(全GC)的编程语言,GC机制能够使开发者从繁琐的内存管理工作中解脱出来,很大程度上提升开发效率和代码的容错性。但是由于GC属于解释层模块,所以业务开发者几乎没有干预空间,一旦出现内存泄漏便只能靠解释器的GC策略进行调整。
GC算法
JavaScript引擎最常见的两种GC算法:标记清除算法、引用计数算法。
标记清除算法是目前应用较广泛的GC算法之一,绝大多数JavaScript引擎的GC算法都是在标记清除算法基础上的变中,比如V8的标记压缩算法。标记清除算法分为两个阶段:标记阶段和清除阶段。在标记阶段以根节点为起点,使用深度优先搜索算法向下遍历所有对象,随后清除阶段把没标记的对象删除,并且把已标记的对象的标记信息也清除以便下次GC流程正常进行。(补充逻辑部分略)
引用计数算法是IE6和IE7引擎采用的GC算法,但是目前已经绝迹。虽然这个已经成为了历史产物,但作为对比,有助于理解标记清除算法。略。
标记清除算法是目前实行JavaScriptGC策略的最佳选择。
内存泄漏
在运行应用程序时,计算机管理内存的一般流程是 分配-使用-释放,如此循环。
内存泄漏指的是一些分配出去的内存空间在使用完后没有释放。这些残留的冗余对象毫无用处却占据内存空间。大量的内存泄漏会造成因内存不足而导致应用程序崩溃甚至宕机。
造成JavaScript内存泄漏的根本原因是不合理的引用。由于JavaScript引擎的GC操作在语言层面是完全封闭的,开发者没有任何干预的权限,只能通过编写合理的代码以避免发生内存泄漏。
内存泄漏的处理方法
避免全局变量。
在全局作用域内创建对象非常容易引发内存泄漏。并且还有命名冲突、破坏封装性、存在安全隐患等弊端;
谨慎处理闭包。
闭包可以在函数内部引用外层作用域的变量,是JavaScript的核心特点之一,但是使用不当很容易造成内存泄漏。
使用编译工具。
前两种方法是尽力写出好的代码。第三种方法就是通过前端自动化工具帮助检测,令code smell自动暴露出来。
极限运算性能
网络体验最差的地方就是:加载缓慢和操作卡顿。加载缓慢的情况仍然存在,操作卡顿的情况慢慢减少,出了代码质量这种不可控的因素外,造成加载缓慢和操作卡顿的原因分别是网络延迟和浏览器的运算能力。造成网络延迟的因素是多方面的,需要从多方面同时切入优化。造成操作卡顿的原因非常单一,浏览器非常有限的运算能力是唯一的瓶颈。不过现代浏览器的运算能力越来越强了,vue等框架使得最消耗性能的DOM操作实现了轻量化和精细化。
不过开发者不能毫无顾忌,业务需求的增长速度远超技术的发展,web应用的体量终有一天会增长至如今的几十倍。并且目前复制图形类web应用(如游戏、VR等)已经非常接近浏览器运算能力的瓶颈。
web worker与并行计算
单线程的JavaScript无法实现并行计算,当浏览器处理计算量庞大的逻辑时会使用户的任何操作均得不到反馈。web worker是HTML5规范的一部分,借助它可以在浏览器后台创建独立的worker线程运行JavaScript代码,实现多线程并行计算。
webAssembly
意思为适合web的汇编语言,是运行与浏览器环境的二进制代码。其定位是应对要求高性能的业务场景,如3D游戏、webVR/AR、音视频等。
WebGPU
对于核心聚焦在交互逻辑和UI的前端来说,设计高性能计算的项目计划没有纯粹的数据计算,绝大多数是复杂的图形类应用。基于此,便可以把图形编程领域的诸多优化策略带入前端领域。
最典型的就是将计算交付给比CPU性能更高的GPU来执行。随着Flash的淘汰,WebGL基本通知复杂图形类web应用的开放市场。webGL的着色器逻辑在GPU中执行,计算性能远高于JavaScript。
题外话
《高性能网站建设指南》的借鉴
其中的14条规则挺有用的,可以借鉴。之后这本书的作者2010年还出版了《高性能网站建设进阶指南》。
别人的笔记可以借鉴一下:《高性能网站建设指南》笔记
最好是看原书。
规则1:尽量减少HTTP请求
规则2:使用CDN
规则3:添加Expires头
规则4:采用Gzip压缩组件
规则5:将样式表放在顶部(使用link标签将样式表放在文档head中)
规则6:将脚本放在底部
规则7:避免CSS表达式
规则8:使用外部JavaScript和CSS
规则9:减少DNS查找
规则10:精简JavaScript
规则11:避免重定向
规则12: 删除重复脚本
规则13:配置ETag(配置或移除ETag)
规则14:使Ajax可缓存
有些内容我还看不太懂。不过其中有不少可以借鉴,如
- 减少HTTP请求数量
- 将CSS放到head中
- 将外部脚本置底
- 资源合并和压缩
- 避免重复的资源请求
- 懒加载
- 代码优化。
把CSS放在头部的原因是,在加载HTML生成DOM树的时候,就可以同时对DOM树进行渲染。这样可以防止闪跳、白屏或者布局混乱;
把JavaScript放在后面的原因是,JavaScript可能会改变DOM树的结构,所以需要一个稳定的DOM树。并且JavaScript加载后会立即执行,同时阻塞后面资源的加载。
最后,本来是想写自己的理解,却没想到这里许多内容都感觉有必要写出来。大家如果感兴趣的话,亲自看书体验更佳。