返回部落格

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 團隊的反饋和合作,我們在 Vite 中使用 SWC 外掛來取代 預設的 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 上開啟問題

開源網路的未來

我們的團隊汲取了 webpack 10 年的經驗,結合 Turborepo 和 Google 的 Bazel 在增量計算方面的創新,並創造了一個準備好支援未來數十年計算的架構。

我們的目標是建立一個開源工具系統,以協助建構 Web 的未來 — 由 Turbopack 提供支援。我們正在建立一個可重複使用的架構,該架構將使每個人的開發和預熱生產建置都更快。

對於 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 的數據。

根節點與葉節點

Evan You 的基準測試應用程式由一個包含數千個導入的大型單一檔案和數千個非常小的檔案組成。我們的基準測試形狀不同——它代表一個檔案樹,每個檔案導入 0 或 3 個其他檔案,試圖模仿一般的應用程式。當編輯他基準測試中的大型根檔案時,Evan 幫助我們找到了一個回歸錯誤。此問題的原因很快被 Tobias 找出並修復,並在 Next 13.0.1 中發布。

我們已調整 HMR 基準測試,以在所有深度均勻地取樣模組,並且我們已更新這篇部落格文章和我們的文件,以包含有關此過程的更多詳細資訊。

SWC 與 Babel

當我們最初發布基準測試時,我們使用的是官方 Vite React 插件,該插件在底層使用 Babel。 Turbopack 本身使用 SWC,它比 Babel 快得多。 Evan You 建議,為了進行更準確的比較,我們應該將 Vite 的基準測試變更為使用 SWC 插件,而不是預設的 Babel 體驗。

雖然 SWC 確實顯著提高了 Vite 的啟動效能,但它在 HMR 更新中僅顯示出很小的差異(<10%)。 應該注意的是,插件之間的 React Refresh 實作方式不同,因此這可能不是在測量 SWC 的效果,而是某些其他實作細節。

我們已更新基準測試,以使用官方 SWC 插件執行 Vite。

檔案監看器的差異

每個作業系統都提供自己的 API 來監看檔案。 在 macOS 上,Turbopack 使用 FSEvents,它顯示出報告更新有 ~12 毫秒的延遲。 我們曾考慮使用 kqueue 來替代,它的延遲時間短得多。 然而,由於它不是一個直接替換方案,並且帶來許多缺點,因此這仍處於探索階段,而不是優先事項。

在 Linux 上,預期會看到不同的數字,其中 inotify 是標準的監控機制。

改進我們的方法

我們的基準測試最初僅從 10 個隔離執行的每個捆綁器的不同實例中取樣 1 個 HMR 更新,總共取樣 10 個更新,並報告平均時間。 在取樣每個更新之前,通過運行 5 個更新來預熱捆綁器實例。

取樣如此少的更新意味著每次測量之間的差異很大,這在 Vite 的 HMR 基準測試中尤為顯著,其中單次更新可能需要 80 到 300 毫秒之間的時間。

我們已將基準測試架構重構為測量每個捆綁器實例的可變更新次數,這會導致每個捆綁器取樣 400 到 4000 個更新。 我們也已將預熱更新的次數增加到 10 次。

最後,我們不是測量所有樣本的平均值,而是測量線性回歸線的斜率。

此變更提高了結果的可靠性。 它還將 Vite 的數字降低到在任何應用程式大小下幾乎恆定的 100 毫秒,而我們之前測量的是對於超過 10,000 個模組的大型應用程式,更新時間超過 200 毫秒。

結論

在與 Vite 團隊就基準測試進行合作後,我們現在測量出 Turbopack 在所有應用程式大小上,HMR 的速度始終比 Vite 快 5 倍。 我們已更新所有基準測試以反映我們的新方法。