性能基础

性能意味着效率。在开放网络应用的背景下,本文档总体解释了什么是性能,浏览器平台如何帮助提升性能,以及你可以使用哪些工具和流程来测试和提升性能。

什么是性能?

最终,用户感知的性能是唯一重要的性能。用户通过触摸、移动和语音向系统提供输入。作为回报,他们通过视觉、触觉和听觉感知输出。性能是系统输出响应用户输入的质量。

在其他条件相同的情况下,如果代码优化的目标不是用户感知性能(以下简称 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)绘制到 canvas 上。

Gecko 渲染

Gecko 的 JavaScript 引擎支持即时(JIT)编译。这使得应用程序逻辑的性能可与 Java 虚拟机等其他虚拟机相媲美,在某些情况下甚至接近“原生代码”。

Gecko 中支撑 HTML、CSS 和 Canvas 的图形管道通过多种方式进行了优化。Gecko 中的 HTML/CSS 布局和图形代码减少了滚动等常见情况的无效和重绘;开发人员“免费”获得此支持。Gecko“自动”绘制的像素缓冲区和应用程序“手动”绘制到 canvas 中的像素缓冲区在绘制到显示帧缓冲区时最大程度地减少了复制。这是通过避免中间表面(如果它们会产生开销,例如许多其他操作系统中每个应用程序的“后台缓冲区”)以及使用可由合成器硬件直接访问的图形缓冲区专用内存来实现的。复杂场景使用设备的 GPU 进行渲染以实现最大性能。为了改善功耗,简单场景使用特殊的专用合成硬件进行渲染,而 GPU 则处于空闲或关闭状态。

对于富应用而言,完全静态内容是例外而非规则。富应用使用具有 animationtransition 效果的动态内容。过渡和动画对应用程序特别重要:开发人员可以使用 CSS 以简单的高级语法声明复杂的行为。反过来,Gecko 的图形管道经过高度优化,可以高效渲染常见动画。常见动画会“卸载”到系统合成器,系统合成器可以以高性能、高能效的方式渲染它们。

应用的启动性能与运行时性能同样重要。Gecko 经过优化,可以高效加载各种内容:整个 Web!多年来针对此内容进行的改进,例如并行 HTML 解析、智能调度重排和图像解码、巧妙的布局算法等,同样适用于改进 Firefox 上的 Web 应用。

应用性能

本节旨在回答开发人员提出的问题:“如何让我的应用更快?”

启动性能

一般而言,应用程序启动会经历三个用户感知的事件:

  • 第一个是应用程序的首次绘制——足以绘制初始帧的应用程序资源已加载完毕的时间点
  • 第二个是应用程序变得可交互——例如,用户能够点击按钮并且应用程序做出响应
  • 最后一个事件是完全加载——例如,音乐播放器中列出了用户的所有专辑

快速启动的关键是牢记两点:UPP(用户感知性能)是唯一重要的,并且每个上述用户感知事件都存在一个“关键路径”。关键路径正是并且仅仅是必须运行才能产生该事件的代码。

例如,要绘制应用程序的第一个框架,该框架在视觉上包含一些 HTML 和用于样式化该 HTML 的 CSS

  1. HTML 必须被解析
  2. 必须构建该 HTML 的 DOM
  3. 该 DOM 部分中的图像等资源必须被加载和解码
  4. CSS 样式必须应用于该 DOM
  5. 样式化的文档必须重排

此列表中没有“加载不常用菜单所需的 JS 文件”;“获取并解码高分列表的图像”等。这些工作项不在绘制第一帧的关键路径上。

这似乎是显而易见的,但为了更快地达到用户感知的启动事件,主要的“诀窍”是只运行关键路径上的代码。通过简化场景来缩短关键路径。

Web 平台具有高度动态性。JavaScript 是一种动态类型语言,Web 平台允许动态加载代码、HTML、CSS、图像和其他资源。这些功能可用于通过在启动后“惰性”加载不必要的内容来延迟非关键路径上的工作。

另一个可能延迟启动的问题是空闲时间,这是由于等待请求响应(如数据库加载)引起的。为了避免这个问题,应用程序应该在启动时尽早发出请求(这称为“预加载”)。这样,当稍后需要数据时,它有望已经可用,并且应用程序不必等待。

注意:有关改进启动性能的更多信息,请阅读 优化启动性能

同样,请注意,本地缓存的静态资源加载速度远快于通过高延迟、低带宽移动网络获取的动态数据。网络请求绝不应出现在早期应用程序启动的关键路径上。可以通过 Service Workers 实现本地缓存/离线应用。请参阅 离线和后台操作 以获取有关使用 Service Workers 实现离线和后台同步功能的指南。

帧速率

高帧率的首要条件是选择正确的工具。使用 HTML 和 CSS 来实现主要是静态的、可滚动的且不经常动画化的内容。使用 Canvas 来实现高度动态的内容,例如需要严格控制渲染且不需要主题的游戏。

对于使用 Canvas 绘制的内容,由开发人员来达到帧率目标:他们可以直接控制绘制的内容。

对于 HTML 和 CSS 内容,实现高帧率的方法是使用正确的原语。Firefox 经过高度优化以滚动任意内容;这通常不是一个问题。但通常为了速度而牺牲一些通用性和质量,例如使用静态渲染而不是 CSS 径向渐变,可以将滚动帧率推高到目标之上。CSS 媒体查询允许将这些折衷限制在需要它们的设备上。

许多应用程序通过“页面”或“面板”使用过渡或动画。例如,用户点击“设置”按钮以过渡到应用程序配置屏幕,或者设置菜单“弹出”。Firefox 经过高度优化,可以对以下场景进行过渡和动画:

  • 使用大小近似于设备屏幕或更小的页面/面板
  • 过渡/动画 CSS transformopacity 属性

遵守这些准则的过渡和动画可以卸载到系统合成器,并以最高效率运行。

内存和电量消耗

改进内存和电量消耗与加速启动是类似的问题:不要做不必要的工作或延迟加载不常用的 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 属性来调整内容的位置、缩放等。或者,你可以使用单独的变换属性,如 translatescalerotate。原因还是硬件加速。浏览器可以在你的 GPU 上完成这些任务,让 CPU 处理其他事情。

此外,变换为你提供了你可能不具备的功能。你不仅可以在 2D 空间中平移元素,还可以在三维空间中变换、倾斜和旋转等等。Paul Irish 从性能角度对 translate() 的优点进行了深入分析(2012 年)。然而,总的来说,你获得了与使用 CSS 动画相同的优点:你使用正确的工具完成工作,并将优化留给浏览器。你还使用了一种易于扩展的元素定位方式——如果你使用 topleft 定位模拟平移,则需要大量的额外代码。另一个好处是,这就像在 canvas 元素中工作一样。

注意:根据平台的不同,如果你希望 CSS 动画获得硬件加速,可能需要添加 translateZ(0.1) 变换。如上所述,这可以提高性能。如果过度使用,可能会导致内存消耗问题。你在这方面的做法取决于你自己——进行一些测试,找出最适合你特定应用程序的方法。

使用 requestAnimationFrame() 而不是 setInterval()

setInterval() 的调用以假定的帧率运行代码,而该帧率在当前情况下可能无法实现。它告诉浏览器即使浏览器实际没有绘制,也要渲染结果;也就是说,即使视频硬件尚未达到下一个显示周期。这会浪费处理器时间,甚至可能导致用户设备电池寿命缩短。

相反,你应该尝试使用 Window.requestAnimationFrame()。它会等到浏览器真正准备好开始构建动画的下一帧时才进行操作,并且如果硬件实际上不会绘制任何东西,它也不会打扰。此 API 的另一个好处是,当你的应用程序在屏幕上不可见时(例如,它在后台并且有其他任务正在运行时),动画将不会运行。这将节省电池寿命,并防止用户在夜空中诅咒你的名字。

让事件立即发生

作为老派的、注重无障碍性的 Web 开发者,我们喜欢点击事件,因为它们也支持键盘输入。但在移动设备上,这些事件太慢了。你应该改用 touchstarttouchend。原因在于它们没有延迟,这会让与应用的交互显得迟钝。如果你首先测试触摸支持,你也不会牺牲无障碍性。例如,金融时报就为此目的使用了一个名为 fastclick 的库,你可以使用它。

保持界面简洁

我们在 HTML 应用程序中发现的一个主要性能问题是,移动大量 DOM 元素会使一切变得迟缓——尤其是在它们具有大量渐变和阴影时。简化你的外观并拖放代理元素会有很大帮助。

例如,如果你有一个很长的元素列表(假设是推文),不要移动所有元素。相反,在你的 DOM 树中只保留那些可见的元素以及当前可见推文集两侧的几个元素。隐藏或删除其余的。将数据保存在 JavaScript 对象中而不是访问 DOM 可以极大地提高你应用程序的性能。将显示视为数据的呈现,而不是数据本身。这并不意味着你不能使用纯 HTML 作为源;只需读取一次,然后滚动 10 个元素,根据你在结果列表中的位置相应地更改第一个和最后一个元素的内容,而不是移动 100 个不可见的元素。同样的技巧也适用于游戏中的精灵:如果它们当前不在屏幕上,则无需轮询它们。相反,将滚动出屏幕的元素重新用作新进入屏幕的元素。

通用应用性能分析

Firefox、Chrome 和其他浏览器都包含内置工具,可以帮助你诊断页面渲染缓慢的问题。特别是,Firefox 的网络监视器将显示页面上每个网络请求发生的精确时间线、请求大小以及所需时间。

The Firefox network monitor showing get requests, multiple files, and different times taken to load each resource on a graph.

如果你的页面包含运行时间很长的 JavaScript 代码,JavaScript 分析器将精确定位最慢的代码行。

The Firefox JavaScript profiler showing a completed profile 1.

内置的 Gecko 分析器是一个非常有用的工具,可以提供更详细的信息,说明在分析器运行时,浏览器代码的哪些部分运行缓慢。这使用起来稍微复杂一些,但提供了许多有用的详细信息。

A built-in Gecko profiler windows showing a lot of network information.

注意:你可以通过运行 Firefox 并启用 about:debugging 来将这些工具与 Android 浏览器一起使用。

特别是,在移动浏览器中进行数十或数百次网络请求需要更长时间。渲染大型图像和 CSS 渐变也可能需要更长时间。即使在高速网络上,下载大文件也可能需要更长时间,因为移动硬件有时太慢,无法利用所有可用带宽。有关移动 Web 性能的有用通用技巧,请查看 Maximiliano Firtman 的 移动 Web 高性能演讲。

测试用例和提交 Bug

如果 Firefox 和 Chrome 开发工具无法帮助你找到问题,或者它们似乎表明是 Web 浏览器导致了问题,请尝试提供一个尽可能隔离问题的精简测试用例。这通常有助于诊断问题。

尝试通过保存和加载 HTML 页面的静态副本(包括其中嵌入的任何图像/样式表/脚本)来重现问题。如果可以,请编辑静态文件以删除任何私人信息,然后将它们发送给其他人寻求帮助(例如,提交 Bugzilla 报告,或者将其托管在服务器上并分享 URL)。你还应该分享使用上述工具收集的任何分析信息。