性能基础
性能意味着效率。在 Open Web Apps 的背景下,本文一般解释了什么是性能,浏览器平台如何帮助改善性能,以及您可以使用哪些工具和流程来测试和改进性能。
什么是性能?
最终,用户感知的性能是唯一重要的性能。用户通过触摸、移动和语音向系统提供输入。作为回报,他们通过视觉、触觉和听觉感知输出。性能是系统响应用户输入的输出质量。
在所有其他条件相同的情况下,针对除用户感知性能(以下简称 UPP)之外的目标进行优化的代码在与针对 UPP 进行优化的代码竞争时会失败。用户更喜欢,例如,一个响应迅速、流畅的应用程序,该应用程序每秒只处理 1,000 个数据库事务,而不是一个卡顿、无响应的应用程序,该应用程序每秒处理 100,000,000 个数据库事务。当然,优化其他指标绝非毫无意义,但真正的 UPP 目标应放在首位。
接下来的几个小节将指明并讨论一些重要的性能指标。
响应能力
响应能力是指系统响应用户输入(可能是多个输入)的速度。例如,当用户点击屏幕时,他们希望像素以某种方式发生变化。对于这种交互,响应能力指标是点击和像素变化之间经过的时间。
响应能力有时会涉及多个反馈阶段。应用程序启动是一个特别重要的案例,我们将在下面更详细地讨论。
响应能力很重要,因为当用户被忽略时,他们会感到沮丧和愤怒。您的应用程序在未能响应用户输入的每一秒都在忽略用户。
帧率
帧率是系统更改显示给用户的像素的速率。这是一个熟悉的概念:例如,每个人都更喜欢显示 60 帧/秒的游戏,而不是显示 10 帧/秒的游戏,即使他们无法解释原因。
帧率作为“服务质量”指标很重要。计算机显示器旨在通过向用户传递模仿现实的光子来“愚弄”用户的眼睛。例如,印有文字的纸张以某种方式将光子反射到用户眼中。通过操纵像素,阅读器应用程序会以类似的方式发射光子来“愚弄”用户的眼睛。
正如您的大脑推断的那样,运动不是间断的和离散的,而是平滑地、连续地“更新”。(频闪灯很有趣,因为它们颠倒了这种情况,让您的大脑缺乏输入以创造离散现实的幻觉)。在计算机显示器上,更高的帧率使对现实的模仿更加真实。
注意: 人类通常无法感知高于 60Hz 的帧率差异。这就是为什么大多数现代电子显示器都设计为以该速率刷新。例如,对于蜂鸟来说,电视可能会显得断断续续、不真实。
内存使用情况
内存使用情况是另一个关键指标。与响应能力和帧率不同,用户不会直接感知内存使用情况,但内存使用情况与“用户状态”非常接近。理想的系统会始终保持 100% 的用户状态:系统中的所有应用程序都会同时运行,并且所有应用程序都会保留用户上次与应用程序交互时创建的状态(应用程序状态存储在计算机内存中,这就是为什么近似值如此之高的原因)。
由此得出一个重要但违反直觉的推论:一个设计良好的系统不会最大限度地提高可用内存。内存是一种资源,而可用内存是未使用的资源。相反,一个设计良好的系统已被优化为使用尽可能多的内存来维护用户状态,同时满足其他 UPP 目标。
这并不意味着系统应该浪费内存。当系统使用超过维护特定用户状态所需内存时,系统就会浪费它可以用来保留其他用户状态的资源。在实践中,没有一个系统可以维护所有用户状态。明智地将内存分配给用户状态是一个重要的考虑因素,我们将在下面更详细地讨论。
功耗
这里讨论的最后一个指标是功耗。与内存使用情况一样,用户只是间接地感知功耗,即他们的设备能够维持所有其他 UPP 目标的时间长短。为了满足 UPP 目标,系统必须仅使用满足要求的最低功耗。
本文的其余部分将根据这些指标讨论性能。
平台性能优化
本节简要概述了 Firefox/Gecko 如何普遍地对低于所有应用程序级别的性能做出贡献。从开发人员或用户的角度来看,这回答了“平台为您做了什么?”这个问题。
Web 技术
Web 平台提供了许多工具,有些工具比其他工具更适合特定任务。所有应用程序逻辑都用 JavaScript 编写。为了显示图形,开发人员可以使用 HTML 或 CSS(即高级声明性语言),或者使用 <canvas>
元素(包括 WebGL)提供的低级命令式接口。介于 HTML/CSS 和 Canvas 之间的则是 SVG,它提供了两者的某些优点。
HTML 和 CSS 极大地提高了生产力,有时会以牺牲帧率或对渲染的像素级控制为代价。文本和图像会自动重新流动,UI 元素会自动接收系统主题,系统还会为开发人员可能没有最初想到的一些用例提供“内置”支持,例如不同分辨率的显示器或从右到左的语言。
canvas
元素为开发人员提供了直接用于绘制的像素缓冲区。这使开发人员能够对渲染进行像素级控制,并精确控制帧率,但现在开发人员需要处理多种分辨率和方向、从右到左的语言等等。开发人员可以使用熟悉的 2D 绘图 API 或 WebGL(一个“接近金属”的绑定,主要遵循 OpenGL ES 2.0)向画布绘制。
Gecko 渲染
Gecko JavaScript 引擎支持即时 (JIT) 编译。这使应用程序逻辑能够与其他虚拟机(例如 Java 虚拟机)相媲美,在某些情况下甚至接近“原生代码”。
Gecko 中支持 HTML、CSS 和 Canvas 的图形管道通过多种方式进行了优化。Gecko 中的 HTML/CSS 布局和图形代码减少了常见情况(如滚动)的无效化和重绘;开发人员可以“免费”获得此支持。Gecko“自动”和应用程序“手动”绘制到canvas
的像素缓冲区在绘制到显示帧缓冲区时,会最大限度地减少复制。这是通过避免在它们会产生开销的地方(例如,许多其他操作系统的每个应用程序“后备缓冲区”)使用中间表面,以及通过使用图形缓冲区的专用内存,该内存可以被合成硬件直接访问来完成的。复杂的场景使用设备的 GPU 渲染,以获得最佳性能。为了改善功耗,简单的场景使用特殊的专用合成硬件渲染,而 GPU 处于空闲状态或关闭。
对于丰富的应用程序来说,完全静态内容是例外而不是规则。丰富的应用程序使用具有 animation
和 transition
效果的动态内容。过渡和动画对于应用程序来说特别重要:开发人员可以使用 CSS 来声明复杂的行为,并使用简单的高级语法。反过来,Gecko 的图形管道已针对高效地渲染常见动画进行了高度优化。常见动画被“卸载”到系统合成器,该合成器可以以高性能、节能的方式渲染它们。
应用程序的启动性能与它的运行时性能一样重要。Gecko 针对高效地加载各种内容进行了优化:整个 Web!针对此内容的多年改进,例如并行 HTML 解析、智能重新流和图像解码调度、巧妙的布局算法等,同样适用于改善 Firefox 上的 Web 应用程序。
应用程序性能
本节适用于提出以下问题的开发人员:“如何让我的应用程序运行更快?”
启动性能
一般来说,应用程序启动由三个用户感知事件组成。
- 第一个是应用程序首次绘制——加载足够的应用程序资源以绘制初始帧的点。
- 第二个是应用程序变为交互式——例如,用户能够点击按钮,应用程序做出响应。
- 最后一个事件是完全加载——例如,当所有用户的专辑在音乐播放器中列出时。
快速启动的关键是牢记两点:UPP 是唯一重要的因素,并且每个用户感知事件都有一个“关键路径”。关键路径完全并且仅是必须运行以产生事件的代码。
例如,要绘制应用程序的第一帧,其中包含一些 HTML 和 CSS 来对该 HTML 进行样式设置。
- 必须解析 HTML。
- 必须构建该 HTML 的 DOM。
- 必须加载和解码 DOM 中该部分的资源(如图像)。
- 必须将 CSS 样式应用于该 DOM。
- 必须重新排列带样式的文档。
该列表中没有“加载非通用菜单所需的 JS 文件”,“获取和解码高分列表的图像”等。这些工作项不在绘制第一帧的关键路径上。
这似乎很明显,但要更快地到达用户感知的启动事件,主要的“技巧”是仅运行关键路径上的代码。通过简化场景来缩短关键路径。
Web 平台高度动态。JavaScript 是一种动态类型语言,Web 平台允许动态加载代码、HTML、CSS、图像和其他资源。这些功能可用于通过在启动后一段时间“延迟”加载不必要的內容来推迟不在关键路径上的工作。
另一个可能延迟启动的问题是空闲时间,这是由等待请求响应(如数据库加载)引起的。为了避免此问题,应用程序应尽早(在启动时)发出请求(这称为“前加载”)。然后,当稍后需要数据时,希望它已经可用,并且应用程序不必等待。
注意:有关提高启动性能的更多信息,请阅读优化启动性能。
同样,请注意,本地缓存的静态资源的加载速度比通过高延迟、低带宽移动网络获取的动态数据快得多。网络请求永远不应该在早期应用程序启动的关键路径上。本地缓存/脱机应用程序可以通过Service Workers 实现。有关使用 Service Workers 实现脱机和后台同步功能的指南,请参阅脱机和后台操作。
帧率
实现高帧率的第一要务是选择合适的工具。使用 HTML 和 CSS 实现主要为静态、滚动和不常动画的内容。使用 Canvas 实现高度动态的内容,例如需要严格控制渲染且不需要主题的游戏。
对于使用 Canvas 绘制的内容,开发者需要自己实现帧率目标:他们对绘制的内容有直接控制权。
对于 HTML 和 CSS 内容,实现高帧率的关键是使用合适的基元。Firefox 对滚动任意内容进行了高度优化;这通常不是问题。但通常会以牺牲一些通用性和质量为代价来换取速度,例如使用静态渲染而不是 CSS 径向渐变,可以将滚动帧率推到目标值之上。CSS 的媒体查询 允许这些折衷只针对需要它们的设备。
许多应用程序使用过渡或动画来切换“页面”或“面板”。例如,用户点击“设置”按钮以切换到应用程序配置屏幕,或设置菜单“弹出”。Firefox 对切换和动画场景进行了高度优化,这些场景
- 使用大小约等于设备屏幕或更小的页面/面板
- 切换/动画 CSS 的
transform
和opacity
属性
符合这些指南的过渡和动画可以卸载到系统合成器,并以最大效率运行。
内存和功耗
改善内存和功耗与加快启动速度类似:不要做不必要的工作,或延迟加载不常用的 UI 资源。要使用高效的数据结构,并确保图像等资源得到充分优化。
现代 CPU 在大部分空闲时可以进入低功耗模式。不断触发计时器或保持不必要的动画运行的应用程序会阻止 CPU 进入低功耗模式。节能应用程序不应该这样做。
当应用程序被发送到后台时,会在其文档上触发一个visibilitychange
事件。这个事件是开发者的朋友;应用程序应该监听它。
应用程序性能的具体编码技巧
以下实用技巧将有助于改善上面讨论的应用程序性能因素之一或多个。
使用 CSS 动画和过渡
不要使用某些库的animate()
函数,该函数可能当前使用许多性能很差的技术(例如,setTimeout()
或top
/left
定位),而是使用CSS 动画。在许多情况下,您实际上可以使用CSS 过渡 来完成任务。这样做效果很好,因为浏览器被设计为优化这些效果并使用 GPU 来以最小的处理器性能影响平滑地处理它们。另一个好处是,您可以使用标准化的语法在 CSS 中定义这些效果以及应用程序的其余外观和风格。
CSS 动画使用关键帧 提供对效果的细粒度控制,您甚至可以观察动画过程期间触发的事件,以便处理动画过程中的特定点需要执行的其他任务。您可以轻松地使用:hover
、:focus
或:target
,或者通过动态添加和删除父元素上的类来触发这些动画。
如果您想动态创建动画或在JavaScript 中修改动画,James Long 为此编写了一个名为CSS-animations.js 的简单库。
使用 CSS 变换
不要自己调整绝对定位和摆弄所有这些数学运算,而是使用transform
CSS 属性来调整内容的位置、缩放等。或者,您可以使用translate
、scale
和rotate
的各个变换属性。再次强调,这是因为硬件加速。浏览器可以在您的 GPU 上完成这些任务,让 CPU 处理其他事情。
此外,变换提供了您可能无法获得的其他功能。您不仅可以在 2D 空间中平移元素,还可以进行三维变换、倾斜和旋转等。Paul Irish 对translate()
的好处(2012 年)从性能角度进行了深入分析。总的来说,您将获得与使用 CSS 动画相同的益处:您使用合适的工具完成任务,并将优化留给浏览器。您还使用了一种易于扩展的方式来定位元素——如果您使用top
和left
定位来模拟平移,则需要额外编写很多代码。另一个好处是,这就像在canvas
元素中工作一样。
注意:如果您希望在 CSS 动画中获得硬件加速,则可能需要附加translateZ(0.1)
变换,具体取决于平台。如上所述,这可以提高性能。过度使用可能会导致内存消耗问题。您在这方面要做什么取决于您——进行一些测试,找出最适合您的特定应用程序的方法。
使用requestAnimationFrame()
代替setInterval()
对setInterval()
的调用以假定的帧率运行代码,该帧率在当前情况下可能无法实现。它告诉浏览器渲染结果,即使浏览器实际上没有绘制;也就是说,在视频硬件尚未达到下一个显示周期时。这会浪费处理器时间,甚至会导致用户设备的电池寿命缩短。
相反,您应该尝试使用window.requestAnimationFrame()
。这将等到浏览器准备好开始构建动画的下一帧,如果硬件实际上没有绘制任何内容,则不会打扰。此 API 的另一个好处是,当您的应用程序在屏幕上不可见时(例如,如果它在后台并且正在执行其他任务),动画不会运行。这将节省电池寿命,并防止用户在夜空中诅咒您的名字。
使事件立即执行
作为老派的、注重无障碍的 Web 开发人员,我们喜欢点击事件,因为它们也支持键盘输入。在移动设备上,这些速度太慢了。您应该使用touchstart
和touchend
来代替。原因是这些事件没有延迟,不会使与应用程序的交互显得迟缓。如果您首先测试触摸支持,也不会牺牲无障碍性。例如,金融时报使用一个名为fastclick 的库来实现这个目的,您可以使用它。
保持界面简洁
我们在 HTML 应用程序中发现的一个重大性能问题是,移动大量的DOM 元素会导致一切变得迟缓——尤其是当它们包含大量渐变和阴影时。简化外观和风格,并在拖放时移动代理元素将非常有帮助。
例如,当您有一长串元素(比如推文)时,不要移动所有元素。相反,在您的 DOM 树中只保留可见的元素以及当前可见推文集两侧的几个元素。隐藏或删除其余元素。将数据保存在 JavaScript 对象中,而不是访问 DOM,可以极大地提高应用程序的性能。将显示视为数据的呈现,而不是数据本身。这并不意味着您不能使用纯 HTML 作为源代码;只需读取一次,然后滚动 10 个元素,相应地更改第一个和最后一个元素的内容以反映您在结果列表中的位置,而不是移动 100 个不可见的元素。同样的技巧适用于游戏中的精灵:如果它们当前不在屏幕上,则无需轮询它们。相反,将滚动出屏幕的元素重新用作新元素。
通用应用程序性能分析
Firefox、Chrome 和其他浏览器包含内置工具,可以帮助您诊断页面渲染缓慢的问题。特别是,Firefox 的网络监视器 将显示页面上每个网络请求发生的精确时间线、大小以及所用时间。
如果您的页面包含运行时间过长的 JavaScript 代码,则JavaScript 分析器 将精确地找出最慢的代码行。
内置 Gecko 分析器 是一款非常有用的工具,可以提供更多关于浏览器代码的哪些部分在分析器运行时运行缓慢的详细信息。这有点复杂,但提供了很多有用的细节。
注意:您可以在 Android 浏览器中使用这些工具,方法是运行 Firefox 并启用about:debugging。
特别是,在移动浏览器中进行数十或数百个网络请求需要更长时间。渲染大型图像和 CSS 渐变也可能需要更长时间。即使在快速网络上,下载大型文件也可能需要更长时间,因为移动硬件有时太慢,无法利用所有可用的带宽。有关移动 Web 性能的实用通用提示,请查看 Maximiliano Firtman 的移动 Web 高性能 演讲。
测试用例和提交错误
如果 Firefox 和 Chrome 开发者工具无法帮助您找到问题,或者它们似乎表明 Web 浏览器导致了问题,请尝试提供一个最大限度地隔离问题的简化测试用例。这通常有助于诊断问题。
看看您是否可以通过保存和加载 HTML 页面的静态副本(包括它嵌入的任何图像/样式表/脚本)来重现问题。如果是,请编辑静态文件以删除任何私人信息,然后将它们发送给其他人以寻求帮助(例如,提交Bugzilla 报告,或者将其托管在服务器上并共享 URL)。您还应分享使用上面列出的工具收集的任何分析信息。