返回博客

Turbopack 性能基准测试

2022 年 10 月 31 日,星期一

最后更新2022 年 12 月 22 日,星期四
Tobias Koppers
姓名
Tobias Koppers
X
@wSokra
Alex Kirszenberg
姓名
Alex Kirszenberg
X
@alexkirsz

摘要


Next.js Conf 上,我们宣布了我们最新的开源项目:Turbopack,一个用 Rust 编写的、针对 JavaScript 和 TypeScript 优化的增量打包器和构建系统。

该项目最初是为了提高 webpack 的性能并创建更容易与未来工具集成的方法而进行的探索。在此过程中,团队意识到需要更大的努力。虽然我们看到了提高性能的机会,但一种可以扩展到世界上最大的项目的新架构的前提令人鼓舞。

在这篇文章中,我们将探讨为什么 Turbopack 如此之快,它的增量引擎如何工作,以及将其与现有方法进行基准测试。

为什么 Turbopack 如此之快?

Turbopack 的速度来自于它的增量计算引擎。与我们在前端状态库中看到的趋势类似,计算工作被拆分为反应式函数,这使 Turbopack 能够将更新应用于现有编译,而无需经过完整的图表重新计算和捆绑生命周期。

这不像传统的缓存那样,在操作之前从缓存中查找结果,然后决定是否使用它。那样太慢了。

相反,Turbopack 完全跳过缓存结果的工作,只重新计算其内部函数依赖关系图中受影响的部分。这使得更新独立于整个编译的大小,并消除了传统缓存的通常开销。

Turbopack、webpack 和 Vite 的基准测试

我们创建了一个测试生成器,它生成一个具有可变数量模块的应用程序,以测试冷启动和文件更新任务。此生成的应用程序包括以下工具的入口点

作为当前最先进的技术,我们包括了 Vite 以及基于 webpack 的 Next.js 解决方案。所有这些工具链都指向相同的生成组件树,在浏览器中组装一个 谢尔宾斯基三角形,其中每个三角形都是一个单独的模块。

此图像是我们运行基准测试的测试应用程序的屏幕截图。它描绘了一个谢尔宾斯基三角形,其中每个三角形都是它自己的组件,在它自己的文件中分隔。在此示例中,屏幕上呈现了 3,000 个三角形。

冷启动时间

此测试衡量本地开发服务器在各种大小的应用程序上启动的速度。我们将其衡量为从启动(无缓存)到应用程序在浏览器中呈现的时间。我们不会在此数据集中等待应用程序在浏览器中交互或水合。

根据与 Vite 团队的反馈和协作,我们使用 SWC 插件 与 Vite 替换 默认的 Babel 插件,以在此基准测试中提高性能。

按模块计数计算的启动时间。基准测试数据来自 16 英寸 MacBook Pro 2021、M1 Max、32GB RAM、macOS 13.0.1 (22A400)。

数据

要自己运行此基准测试,请克隆 vercel/turbo,然后从根目录使用此命令

终端
TURBOPACK_BENCH_COUNTS=1000,5000,10000,30000 TURBOPACK_BENCH_BUNDLERS=all cargo bench -p turbopack-bench "startup/(Turbopack SSR|Next.js 12 SSR|Next.js 11 SSR|Vite SWC CSR)."

以下是我们能够在 16 英寸 MacBook Pro 2021、M1 Max、32GB RAM、macOS 13.0.1 (22A400) 上生成的数据

终端
bench_startup/Next.js 11 SSR/1000 modules                  9.2±0.04s
bench_startup/Next.js 11 SSR/5000 modules                 32.9±0.67s
bench_startup/Next.js 11 SSR/10000 modules                71.8±2.57s
bench_startup/Next.js 11 SSR/30000 modules               237.6±6.43s
bench_startup/Next.js 12 SSR/1000 modules                  3.6±0.02s
bench_startup/Next.js 12 SSR/5000 modules                 12.1±0.32s
bench_startup/Next.js 12 SSR/10000 modules                23.3±0.32s
bench_startup/Next.js 12 SSR/30000 modules                89.1±0.21s
bench_startup/Turbopack SSR/1000 modules               1381.9±5.62ms
bench_startup/Turbopack SSR/5000 modules                   4.0±0.04s
bench_startup/Turbopack SSR/10000 modules                  7.3±0.07s
bench_startup/Turbopack SSR/30000 modules                 22.0±0.32s
bench_startup/Vite SWC CSR/1000 modules                    4.2±0.02s
bench_startup/Vite SWC CSR/5000 modules                   16.6±0.08s
bench_startup/Vite SWC CSR/10000 modules                  32.3±0.12s
bench_startup/Vite SWC CSR/30000 modules                  97.7±1.53s

文件更新 (HMR)

我们还衡量从将更新应用于源文件到浏览器中重新呈现相应更改时,开发服务器的工作速度。

对于热模块重载 (HMR) 基准测试,我们首先使用测试应用程序在全新安装上启动开发服务器。我们通过运行更新直到一个成功来等待 HMR 服务器启动。此步骤很重要,因为它防止了冷流程可能出现的差异。

一旦我们的工具启动完成,我们将对测试应用程序中的模块列表进行一系列更新。模块以随机方式采样,分布确保我们测试每个模块深度的统一模块数量。模块的深度是它在依赖关系图中与入口模块的距离。例如,如果入口模块 A 导入模块 B,模块 B 导入模块 C 和 D,则入口模块 A 的深度为 0,模块 B 的深度为 1,模块 C 和 D 的深度为 2。模块 A 和 B 将具有相等的采样概率,但模块 C 和 D 的采样概率只有一半。

我们将数据点的线性回归斜率报告为目标度量。这是工具将更新应用于应用程序所需的平均时间的估计值。

按模块计数计算的 Turbopack、Next.js (webpack) 和 Vite HMR。基准测试数据来自 16 英寸 MacBook Pro 2021、M1 Max、32GB RAM、macOS 13.0.1 (22A400)。
按模块计数计算的 Turbopack 和 Vite HMR。基准测试数据来自 16 英寸 MacBook Pro 2021、M1 Max、32GB RAM、macOS 13.0.1 (22A400)。

结论:Turbopack 的性能是更新大小的函数,而不是应用程序的大小。

数据

要自己运行此基准测试,请克隆 vercel/turbo,然后从根目录使用此命令

终端
TURBOPACK_BENCH_COUNTS=1000,5000,10000,30000 TURBOPACK_BENCH_BUNDLERS=all cargo bench -p turbopack-bench "hmr_to_commit/(Turbopack SSR|Next.js 12 SSR|Next.js 11 SSR|Vite SWC CSR)"

以下是我们能够在 16 英寸 MacBook Pro 2021、M1 Max、32GB RAM、macOS 13.0.1 (22A400) 上生成的数据

终端
bench_hmr_to_commit/Next.js 11 SSR/1000 modules         211.6±1.14ms
bench_hmr_to_commit/Next.js 11 SSR/5000 modules        866.0±34.44ms
bench_hmr_to_commit/Next.js 11 SSR/10000 modules           2.4±0.13s
bench_hmr_to_commit/Next.js 11 SSR/30000 modules           9.5±3.12s
bench_hmr_to_commit/Next.js 12 SSR/1000 modules         146.2±2.17ms
bench_hmr_to_commit/Next.js 12 SSR/5000 modules        494.7±25.13ms
bench_hmr_to_commit/Next.js 12 SSR/10000 modules     1151.9±280.68ms
bench_hmr_to_commit/Next.js 12 SSR/30000 modules           6.4±2.29s
bench_hmr_to_commit/Turbopack SSR/1000 modules           18.9±2.92ms
bench_hmr_to_commit/Turbopack SSR/5000 modules           23.8±0.31ms
bench_hmr_to_commit/Turbopack SSR/10000 modules          23.0±0.35ms
bench_hmr_to_commit/Turbopack SSR/30000 modules          22.5±0.88ms
bench_hmr_to_commit/Vite SWC CSR/1000 modules           104.8±1.52ms
bench_hmr_to_commit/Vite SWC CSR/5000 modules           109.6±3.94ms
bench_hmr_to_commit/Vite SWC CSR/10000 modules          113.0±1.20ms
bench_hmr_to_commit/Vite SWC CSR/30000 modules         133.3±23.65ms

提醒一下,Vite 在这些基准测试中使用了官方的 SWC 插件,这不是默认配置。

访问 Turbopack 基准测试文档 以自己运行基准测试。如果您对基准测试有疑问,请在 GitHub 上提出问题

开源 Web 的未来

我们的团队吸取了 webpack 10 年的经验教训,结合了来自 Turborepo 和 Google 的 Bazel 的增量计算创新,创建了一种可以支持未来几十年计算的架构。

我们的目标是创建一个开源工具系统,该系统有助于构建以 Turbopack 为动力的 Web 的未来。我们正在创建一个可重用的架构,这将使每个人的开发和预热生产构建更快。

对于 Turbopack 的 alpha 版本,我们将其包含在 Next.js 13 中。但是,随着时间的推移,我们希望 Turbopack 将为其他框架和构建器提供支持,作为一个无缝、低级别、增量的引擎,以构建出色的开发者体验。

我们期待成为社区的一份子,为开发者带来更好的工具,以便他们能够继续为最终用户提供更好的体验。如果您想了解有关 Turbopack 基准测试的更多信息,请访问 turbo.build。要在 Next.js 13 中试用 Turbopack,请访问 nextjs.org


更新 (2022/12/22)

当我们首次发布 Turbopack 时,我们对其相对于以前的 Next.js 版本(11 和 12)以及相对于 Vite 的性能进行了一些声明。这些数字是通过我们的基准测试套件计算得出的,该套件在 turbo 代码仓库上公开可用,但我们没有过多地撰写有关它们的内容,也没有提供有关如何运行它们的明确说明。

在与 Vite 的核心贡献者 Evan You 合作后,我们发布了这篇博客文章,解释了我们的方法,并更新了我们的网站,以提供有关如何运行基准测试的说明。

根据我们与 Vite 合作的结果,以下是我们对上述基准测试中测试方法所做的一些澄清

原始 HMR 与 React Refresh

在我们最初发布的数字中,我们测量的是文件更改到浏览器中运行更新之间的时间,但没有测量 React Refresh 重新渲染更新所需的时间 (hmr_to_eval)。

我们还有另一个包含 React Refresh 的基准 (hmr_to_commit),我们选择不使用它,因为我们认为它主要考虑了 React 的开销——额外的 30 毫秒。然而,这个假设被证明是错误的,问题出在Next.js 的更新处理代码中

另一方面,Vite 的 hmr_to_evalhmr_to_commit 数字非常接近(没有 30 毫秒的差异),并且 这引起了我们对基准测试方法存在缺陷以及我们没有测量正确内容的怀疑

此博客文章已更新,包含 React Refresh 的数据。

根节点 vs. 叶节点

尤雨溪的基准测试应用程序由一个包含一千个导入的非常大的文件和一千个非常小的文件组成。我们的基准测试形状不同——它代表一个文件树,每个文件导入 0 或 3 个其他文件,试图模拟一个平均应用程序。当在尤雨溪的基准测试中编辑大型根文件时,他帮助我们找到了一个回归问题。造成这种情况的原因是 Tobias 迅速地识别并修复了它,并在 Next 13.0.1 中发布。

我们已经调整了 HMR 基准测试,以统一采样所有深度的模块,并且我们已经更新了此博客文章和我们的文档,以包含有关此过程的更多详细信息。

SWC vs. Babel

当我们最初发布基准测试时,我们使用的是官方的 Vite React 插件,该插件在底层使用 Babel。 Turbopack 本身使用 SWC,它比 Babel 快得多。尤雨溪建议,为了进行更准确的比较,我们应该将 Vite 的基准测试更改为使用 SWC 插件,而不是默认的 Babel 体验。

虽然 SWC 确实显著提高了 Vite 的启动性能,但它在 HMR 更新方面只显示出很小的差异(<10%)。应该注意的是,插件之间的 React Refresh 实现是不同的,因此这可能不是在测量 SWC 的效果,而是其他一些实现细节。

我们已经更新了基准测试,以使用官方的 SWC 插件运行 Vite。

文件监视器差异

每个操作系统都提供自己的 API 来监视文件。在 macOS 上,Turbopack 使用 FSEvents,它已被证明在报告更新方面具有 约 12 毫秒的延迟。我们曾考虑使用 kqueue,它具有更低的延迟。但是,由于它不是一个直接的替代品,并且带来了很多缺点,这仍然处于探索阶段,而不是优先事项。

在 Linux 上,预计会看到不同的数字,其中 inotify 是标准的监视机制。

改进我们的方法

我们的基准测试最初只从 10 个独立的每个 bundler 运行实例中采样 1 个 HMR 更新,总共采样了 10 个更新,并报告了平均时间。在采样每个更新之前,通过运行 5 个更新来预热 bundler 实例。

采样如此少的更新意味着从一个测量到下一个测量存在很大的差异,这在 Vite 的 HMR 基准测试中尤为明显,其中单个更新可能需要 80 到 300 毫秒不等。

此后,我们重构了基准测试架构,以测量每个 bundler 实例的可变更新数量,这导致每个 bundler 采样 400 到 4000 个更新。我们还将预热更新的数量增加到 10 个。

最后,我们没有测量所有样本的平均值,而是测量线性回归线的斜率。

此更改提高了我们结果的可靠性。它还将 Vite 的数字降低到在任何应用程序大小下几乎恒定的 100 毫秒,而我们之前测量的是超过 10,000 个模块的较大应用程序的 200 毫秒以上的更新。

结论

在与 Vite 团队合作进行基准测试之后,我们现在测量到 Turbopack 的 HMR 速度始终比 Vite 快 5 倍,适用于所有应用程序大小。我们已更新所有基准测试以反映我们新的方法。