推出 SquirrelFish Extreme

仅仅三个月前,WebKit 团队宣布推出 SquirrelFish,这是我们 JavaScript 引擎的一次重大改造,其特点是高性能字节码解释器。今天,我们很高兴宣布推出我们下一代 JavaScript 引擎——SquirrelFish Extreme(简称 SFX)。SquirrelFish Extreme 采用更先进的技术,包括快速本地代码生成,以提供更卓越的 JavaScript 性能。

对于那些关注 WebKit 开发并有兴趣贡献的人,我们想报告我们的成果以及我们如何实现这些成果。

它有多快?

此图表显示了 WebKit 在不同版本中的 JavaScript 性能——条形越长表示性能越好。

bar graph showing WebKit 3.0: 5.4; WebKit 3.1: 18.8; SquirrelFish: 29.9; SquirrelFish Extreme: 63.6

衡量标准是每分钟 SunSpider 运行次数。我们以这种方式呈现图表,因为当您有广泛的性能结果时,“越大越好”更容易理解。如您所见,截至今天,SquirrelFish Extreme 的速度是最初 SquirrelFish 的两倍多,并且比不到一年前您在 Safari 3.0 中看到的速度快 10 倍以上。我们对这一改进感到非常满意,但我们相信未来还会有更多的性能提升。

不少人对这些成果做出了贡献。我将提及一些从事关键任务的人,但我也要感谢所有为 JavaScript 和性能提供帮助的 WebKit 贡献者。

是什么让它如此之快?

SquirrelFish Extreme 使用四种不同的技术来提供比原始 SquirrelFish 更好的性能:字节码优化、多态内联缓存、轻量级“上下文线程化”JIT 编译器,以及使用我们 JIT 基础设施的新正则表达式引擎。

1. 字节码优化

当我们首次宣布 SquirrelFish 时,我们提到我们认为其基本设计在字节码级别的优化方面有很大的改进空间。感谢 Oliver Hunt、Geoff Garen、Cameron Zwarich、我自己和其他人的辛勤工作,我们在字节码级别实现了许多有效的优化。

我们所做的一件事是优化操作码内部。许多 JavaScript 操作是高度多态的——它们在许多不同情况下具有不同的行为。仅仅通过首先检查最常见和最快的情况,就可以大大加快 JavaScript 程序的运行速度。

此外,我们改进了字节码指令集,并构建了利用这些改进的优化。我们添加了组合指令、窥孔优化、更快的常量处理以及一些针对常见通用操作的专用操作码。

2. 多态内联缓存

SquirrelFish Extreme 中最令人兴奋的新优化之一是多态内联缓存。这是一种最初为 Self 语言开发的老技术,其他 JavaScript 引擎也曾成功使用过。

基本思想是:JavaScript 在设计上是一种极其动态的语言。但在大多数程序中,许多对象实际上以类似于更结构化的面向对象类的方式使用。例如,许多 JavaScript 库旨在仅使用具有“x”和“y”属性的对象来表示点。我们可以利用这些知识来优化许多对象具有相同底层结构的情况——正如动态语言社区的人所说,“只要不被发现,你就可以作弊”。

那么我们到底是如何作弊的呢?我们检测对象何时实际具有相同的底层结构——相同顺序的相同属性——并将它们与结构标识符(或 StructureID)关联起来。每当执行属性访问时,我们第一次会进行常规的哈希查找(使用我们高度优化的哈希表),并记录 StructureID 和找到属性的偏移量。随后的时间,我们检查 StructureID 是否匹配——通常同一段代码将处理相同结构的对象。如果命中,我们就可以使用缓存的偏移量在仅几条机器指令内执行查找,这比哈希查找快得多。

这是描述原始技术的经典 Self 论文。您可以查看 Subversion 中 Geoff 对 StructureID 类的实现,以了解我们是如何做到的更多细节。

我们才刚刚开始多态内联缓存的实践。我们有很多想法来改进这项技术以获得更快的速度。但即使是现在,您也会在以对象属性访问为瓶颈的性能测试中看到巨大的差异。

3. 上下文线程化 JIT

我们对 SFX 所做的另一个重大改变是引入了本地代码生成。我们的起点是一种名为“上下文线程化解释器”的技术,这个名称有点误导,因为它实际上是一种简单但有效的 JIT 编译器形式。在最初的 SquirrelFish 公告中,我们描述了我们对直接线程化的使用,这是在不生成本地代码的情况下最快的字节码解释形式。上下文线程化迈出了下一步,引入了一些本地代码生成。

上下文线程化的基本思想是将字节码一次一个操作码地转换为本地代码。复杂的操作码被转换为对语言运行时的函数调用。简单的操作码,或者在某些情况下,复杂操作码的常见快速路径,则直接内联到本地代码流中。这有两个主要优点。首先,操作码之间的控制流作为直线代码直接暴露给 CPU,从而大大减少了调度开销。其次,许多以前在操作码实现内部的分支现在已内联,并变得对 CPU 的分支预测器可见且高度可预测。

这里有一篇描述上下文线程化基本思想的论文。我们的上下文线程化初始原型由 Gavin Barraclough 创建。我们中的几个人在过去几周里帮助他完善并调整了性能。

我们轻量级 JIT 的一个优点是,本地代码生成仅涉及大约 4,000 行代码。所有其他代码都保持跨平台。它也出奇地易于修改。如果您认为编译成本地代码是火箭科学,那您就错了。除了 Gavin,我们大多数人以前都没有本地代码生成的相关经验,但我们都能够迅速投入其中。

目前代码仅限于 x86 32 位,但我们计划重构并增加对更多 CPU 架构的支持。尚未受 JIT 支持的 CPU 仍然可以使用解释器。我们还认为,通过类型特化、更好的寄存器分配和活跃度分析等技术,我们可以从 JIT 中获得更多的加速。SquirrelFish 字节码是进行许多此类转换的良好表示。

4. 正则表达式 JIT

在我们为主要 JavaScript 语言构建基本 JIT 基础设施时,我们发现可以轻松地将其应用于正则表达式,并在正则表达式匹配方面获得高达 5 倍的加速。于是我们着手进行了这项工作。并非所有代码都花费大量时间在正则表达式上,但凭借我们新的正则表达式引擎 WREC(WebKit 正则表达式编译器)的速度,您可以使用 JavaScript 来编写您希望在 Perl、Python 或 Ruby 中完成的文本处理代码。事实上,我们相信在许多情况下,我们的正则表达式引擎将超越其他语言中经过高度优化的正则表达式处理。

由于 SunSpider JavaScript 基准测试包含相当数量的正则表达式内容,有些人可能会觉得开发一个正则表达式 JIT 是一种“不公平”的优势。一年前,正则表达式处理在测试中只占很小一部分,但 JS 引擎在其他方面的改进远超正则表达式。例如,SunSpider 上的大多数独立测试在 JavaScriptCore 中速度提升了 5-10 倍——在某些情况下比 Safari 3.0 版本的 WebKit 快 70 倍以上。但直到最近,正则表达式的性能几乎没有改善。

我们认为让正则表达式更快比改变基准测试更好。网络上许多实际任务都涉及大量的正则表达式处理。毕竟,网络上的基本任务,如 JSON 验证和解析,都依赖于正则表达式。而且新兴技术——例如John Resig 的 processing.js 库——进一步扩展了这种依赖性。

关于基准测试

我们已经包含了一些性能结果,但不要只听我们的一面之词。您可以获取适用于Mac和 Windows 的 WebKit 夜间构建版本,亲自尝试。

我们用于跟踪 JavaScript 性能的主要基准测试是 SunSpider。尽管像所有基准测试一样,它也有其缺陷,但我们认为它是一个平衡的测试,涵盖了 JavaScript 语言的许多方面和多种类型的代码。如果您逐个查看测试结果,您会发现不同的 JavaScript 实现有其各自的优缺点。浏览器厂商和独立测试人员一直在跟踪这个基准测试。

后续步骤以及如何贡献

我们相信 SquirrelFish Extreme 架构还有很大的优化空间,我们希望看到更多的开发人员和测试人员参与进来。目前,我们正在研究如何利用字节码基础设施在运行时收集更多信息,然后利用这些信息来驱动更好的代码生成,并且我们正在研究使 JS 函数调用更快的方法。还有很多基本的调整工作要做,以更好地利用 SFX 的基本架构改进。此外,我们有兴趣为其他 CPU 架构提供 JIT 后端。

如果您想更密切地关注 WebKit JavaScript 引擎的开发,我们创建了 squirrelfish-dev@lists.webkit.org 邮件列表(在此订阅)和 FreeNode IRC 网络上的 #squirrelfish IRC 频道。欢迎加入,您可以了解更多关于我们计划的信息,以及您如何提供帮助。

尝试一下

试试它,测试它,用它来浏览。它现在已在夜间构建版本中提供。我们希望我们所做的更改有助于改善您的网页体验。

更新:对于好奇的人,这里有一些SFX 与其他主流 JavaScript 引擎的比较。Charles Ying 在更多基准测试中进行了比较

更新 2:对于那些对我们小吉祥物爱不释手的人,点击下方最近 WebKit 夜间构建版本中的 SquirrelFish,即可观看 SVG 动画支持的演示。

the SquirrelFish mascot