Speedometer
Web 应用响应速度基准测试

今天我们很高兴地宣布 Speedometer,这是一个衡量网络应用响应速度的新基准测试。

基准测试

Speedometer 测量网络应用中模拟的用户交互。Speedometer 1.0 版本使用 TodoMVC 来模拟用户添加、完成和删除待办事项的操作。Speedometer 使用 DOM API 重复相同的操作——这是一组在网络应用中广泛使用的核心网络平台 API——以及六个流行的 JavaScript 框架: Ember.jsBackbone.jsjQuery、 AngularJSReactFlight。这些框架中有许多被用于世界上最受欢迎的网站,例如 Facebook 和 Twitter。这些操作的性能取决于 DOM API 的速度、JavaScript 引擎、CSS 样式解析、布局以及其他技术。

动机

去年,当我们着手改进 WebKit 中交互式网络应用的性能时,我们寻找了一个基准测试来指导我们的工作。然而,我们检查的许多浏览器基准测试都是微基准测试,它们无法反映 DOM API 在实际世界中的使用方式,也无法反映单个 API 与网络浏览器引擎其他部分的交互方式。

例如,一个流行的 DOM 基准测试在一个循环中反复将 element.id 的值赋给一个全局变量

for (var i = 0; i < count; i++)
     globalVariable = element.id; 

我们喜欢这些微基准测试,因为它们可以追踪 element.id 等常用 DOM API 中的性能退化。然而,我们无法用它们来指导我们的性能工作,因为它们没有告诉我们每个 DOM API 的相对重要性。有了这些微基准测试,我们很容易就会过度优化那些在实际网络应用中并不那么重要的 API。

这些微基准测试也可能鼓励浏览器厂商实现那些无法转化为实际收益的优化。例如,在上面的例子中,一些浏览器引擎会检测到 element.id 没有副作用,并完全消除循环;只赋值一次。然而,实际网站很少在不使用结果或不修改 DOM 的情况下反复访问 element.id。

因此,我们决定编写一个新的基准测试,用于测量完整网络应用的端到端性能,而不是测试单个 DOM 调用。

机制

我们尝试让 Speedometer 通过重放一系列用户交互,忠实地模拟演示应用上的典型工作负载。然而,我们确实不得不规避浏览器的某些限制。例如,我们对每个复选框调用 click() 以模拟鼠标点击,因为许多浏览器不允许网页内容创建虚假的鼠标或键盘事件。为了使运行时间足够长,以便在有限的精度下进行测量,我们同步执行了大量的操作,例如添加一百个待办事项。

我们还注意到,一些浏览器引擎采用了一种优化策略,即异步执行部分工作以减少同步操作的运行时间。尽快将控制权返回给 JavaScript 执行是值得追求的。然而,对网络应用性能进行整体、准确的测量,需要测量这些相关的异步计算何时实际完成,因为它们仍然可能占用实现流畅的 60 帧每秒用户交互所需的 16 毫秒每帧预算的很大一部分。因此,在 Speedometer 中,我们测量浏览器执行这些异步任务所花费的时间,该时间估计为零秒延迟计时器被调度到它被触发之间的时间。

值得注意的是,Speedometer 并非旨在比较不同 JavaScript 框架的性能。我们模拟用户操作的机制对于每个框架都是不同的,而且在某些情况下,我们强制框架同步执行比所需更多的工作,以确保可以测量运行时间。

WebKit 中的优化

在过去的八个月里,自从我们以不同的名称推出了 第一个测试版 Speedometer 后,我们一直在优化 WebKit 在此基准测试上的性能。

我们在 Speedometer 上最大的改进之一来自于 使渲染树创建变得惰性。在 WebKit 中,我们为屏幕上显示的每个元素创建渲染对象,以便计算每个元素的样式和位置。为了避免抖动,我们修改了 WebKit,使其仅在需要时才创建元素的渲染对象,这极大地提升了 WebKit 的性能。我们还使许多 DOM API 不触发同步样式解析或同步布局,从而从惰性创建渲染树中获得进一步的益处。

我们取得显著改进的另一个领域是 JavaScript 绑定,它是 C++ 浏览器代码和 JavaScript 之间的层。我们移除了多余的抽象层,并使 DOM 对象上更多的属性和成员函数可以内联缓存(参见 WebKit FTL JIT 简介)。例如,我们 添加了一项新的 JavaScriptCore 功能 来处理文档对象上的命名属性,以便其属性可以被内联缓存。我们还优化了 getElementsByTagName 返回的节点列表,并对 length 属性进行了内联缓存。

最后,WebKit 在 Speedometer 上的性能受益于两个主要的架构更改。JavaScriptCore 的 并发 和 并行 JIT(参见 WebKit FTL JIT 简介),它允许 JavaScript 在主线程运行其他代码时进行编译,从而减少了 Speedometer 中 JavaScript 的运行时间。CSS JIT,它允许 CSS 选择器预先编译并快速检查元素,减少了样式解析所花费的时间,并使 querySelector 和 querySelectorAll 变得更快。

由于 Speedometer 是一个使用流行 JavaScript 框架的端到端基准测试,它也帮助我们发现了令人惊讶的性能退化。例如,当我们尝试通过在 Function.prototype.bind 中预先做更多工作来优化 JavaScript 中绑定函数的调用时,我们发现 Speedometer 的性能下降了几个百分点,因为许多绑定函数在被丢弃之前只被调用了一次。我们最初怀疑这个结果是否反映了真实网络应用的行为,因此我们收集了 Facebook 和 Twitter 等流行网站的统计数据。令我们惊讶的是,我们发现了完全相同的行为:在我们研究的网站上,平均每个绑定函数只被调用了一到两次。

未来计划

在未来的版本中,我们希望添加更多种类的网络应用和框架。如果您知道任何根据 MIT 许可 或 BSD 许可 分发且可以集成到 Speedometer 中的优秀演示网络应用, 请告诉我们

有了 Speedometer,网络浏览器社区现在拥有了一个可以衡量实际网络应用响应速度的基准测试。我们期待使用 Speedometer 在 WebKit 中实现进一步的性能改进,并希望其他浏览器厂商也能加入我们。