WebAssembly,一种新的 Web 二进制执行格式,正在开始出现在 浏览器稳定版 中。WebAssembly 的主要目标是**速度快**。这篇文章将提供一些关于 WebAssembly 如何实现这一目标的技术细节。
当然,“快”是相对的。与 JavaScript 和其他动态语言相比,WebAssembly 速度很快,因为它 是静态类型的,并且易于优化。但 WebAssembly 也旨在与原生代码一样快。 asm.js 已经 非常接近于原生代码,而 WebAssembly 进一步缩小了差距。因此,这篇文章重点介绍了为什么 WebAssembly 比 asm.js 更快。
在我们开始之前,先说明一下通常的注意事项:性能难以衡量,并且具有许多方面。此外,在新的技术中,总会有一些尚未优化的案例。因此,并非所有的基准测试在今天的 WebAssembly 上都能运行得很快。这篇文章描述了为什么 WebAssembly **应该**很快;如果它还没有很快,那么这些就是我们需要修复的 bug。
话虽如此,以下就是 WebAssembly 速度很快的原因:
1. 启动
WebAssembly 被设计为体积小、下载快、解析快,以便即使大型应用程序也能快速启动。
实际上,改进压缩后的最小化 JavaScript 的下载大小并不容易,因为它与原生代码相比 已经相当紧凑。不过,WebAssembly 的二进制格式 可以通过精心设计以尺寸为重点来改进这一点(索引是 LEB128 等)。它通常比 asm.js 小 10% 到 20%(比较压缩后的尺寸)。
WebAssembly 在解析方面有更大的改进:它可以 比 JavaScript 快一个数量级。这主要归结于二进制格式解析速度更快,特别是那些为此而设计的格式。WebAssembly 还使并行解析(和优化)函数变得容易,这在多核机器上非常有用。
总的启动时间可能包括下载和解析之外的因素,例如虚拟机完全优化代码,或者下载执行之前必要的其他数据文件等等。但下载和解析是**不可避免的**,因此尽可能地改进它们非常重要。其他一切都可以通过浏览器或应用程序进行优化或缓解(例如,可以通过使用 基线编译器 或 WebAssembly 的解释器来避免完全优化代码,以便前几帧能够正常工作)。
2. CPU 特性
使 asm.js 速度很快的一招是,虽然所有 JavaScript 数字都是双精度浮点数,但在 asm.js 中,加法操作之后会进行一个按位与操作,这在逻辑上等同于 CPU 执行一个简单的整数加法,而 CPU 擅长处理这种情况。因此,asm.js 使虚拟机更容易利用 CPU 的全部性能。
但 asm.js 受限于可以用 JavaScript 表示的内容。WebAssembly 没有这种限制,它允许我们使用更多 CPU 特性,例如:
- 64 位整数。对它们的运算速度可以 快 4 倍。例如,这可以加速哈希和加密算法。
- 加载和存储偏移量。这非常普遍地帮助了所有使用具有固定偏移量的字段的内存对象的内容(C 结构体等等)。
- 未对齐的加载和存储,避免了 asm.js 对掩码的需求(asm.js 这样做是为了与 Typed Array 兼容)。这有助于几乎所有加载和存储操作。
- 各种 CPU 指令,例如 popcount、copysign 等等。每个指令都可以在特定情况下提供帮助(例如,popcount 可以帮助 密码分析)。
特定基准测试的收益多少将取决于它是否使用了上述特性。与 asm.js 相比,我们通常会看到 平均 5% 的速度提升。预计将来会有更多来自 CPU 特性的速度提升,例如 SIMD。
3. 工具链改进
WebAssembly 主要是一个编译器目标,因此它有两个部分:生成它的编译器(工具链方面)和运行它的虚拟机(浏览器方面)。良好的性能取决于这两者。
这对 asm.js 来说已经不是什么新鲜事, Emscripten 做了很多工具链优化,运行了 LLVM 的优化器以及 Emscripten 的 asm.js 优化器。对于 WebAssembly,我们 在其基础上构建,同时还增加了一些显著的改进。asm.js 和 WebAssembly 都不属于典型的编译器目标,它们在相似的方式上,因此在 asm.js 时代积累的经验教训帮助我们更好地为 WebAssembly 做事情。特别地:
- 我们用 Binaryen WebAssembly 优化器替换了 Emscripten 的 asm.js 优化器,它是为速度而设计的。这种速度使我们能够运行更多成本高昂的优化流程。例如,我们在优化时 默认删除重复函数,这通常会使大型编译后的 C++ 代码库缩小约 5%。
- 改进对不可约和复杂控制流的优化,改进 Relooper 算法。这对于编译后的解释器类型的循环非常有用。
- Binaryen 优化器是在实验性设计思想下设计的,并且 超优化方面的实验 导致了一些零星的改进——如果我们想到了,这些也可以在 asm.js 中实现。
总的来说,这些工具链改进与从 asm.js 转移到 WebAssembly 帮助我们的程度差不多(分别在 Box2D 上获得了 7% 和 5% 的提升)。
4. 可预测的良好性能
asm.js 可以 以接近原生速度运行,但它从未在所有浏览器中始终如一地做到这一点。原因是有些人试图用一种方法优化它,而另一些人试图用另一种方法优化它,结果却各不相同。随着时间的推移,情况 开始趋于一致,但根本问题是 asm.js 不是真正的标准:它是由一家供应商编写的 JavaScript 子集的非正式规范,其他供应商只是逐渐对它感兴趣并采用它。
另一方面,WebAssembly 是由所有主要浏览器共同设计的。与 JavaScript 不同的是,JavaScript 只能通过非常 有创意的方法 来实现快速运行,或者 asm.js,它可以用简单的方法实现快速运行,但并非所有浏览器都这样做,WebAssembly 在如何优化它方面达成了一致意见。虚拟机之间仍然有很大的差异空间(不同的分层编译方法、AOT vs. JIT 等等),但可以预期整个 Web 上都能够获得良好的可预测性能基线。
关于 Alon Zakai
Alon 是 Mozilla 研究团队的一员,他主要负责 Emscripten,这是一款将 C 和 C++ 编译成 JavaScript 的编译器。Alon 在 2010 年创建了 Emscripten 项目。
4 条评论