尤雨溪回應:Vite 真的比 Turbopack 慢 10 倍?
一周前,Vercel 宣布了 Webpack 的基于 Rust 的繼任者 Turbopack。
在公告中,Turbopack 宣稱“比 Vite 快 10 倍”。Vercel 的各種營銷材料都重復宣揚這句話,包括推文,博客文章和發(fā)送給 Vercel 用戶的營銷電子郵件。
Turbopack 的文檔中還包括了 benchmark 圖,最初表明,使用 TurboPack 的 Next.js 13 可以在 0.01s 中執(zhí)行 React HMR 熱更新,而對于 Vite 來說需要 0.09s。也有用于冷啟動性能的 benchmarks,但是由于沒有發(fā)現(xiàn)冷啟動速度是 Vite 10 倍的比較,因此我們只能假設“10 倍快”是基于 HMR 的性能。
Vercel 沒有在營銷材料或文檔中使用用于論證這些數(shù)字的 benchmarks 的任何鏈接。因此,我很好奇,并決定使用剛發(fā)布的 Next 13 和 Vite 3.2 的 benchmark 來驗證自己的主張。代碼和方法在此處[1]開源。
我的方法的要點是通過測量以下兩個時間戳之間的增量來比較 HMR 性能:
- 修改源文件的時間,通過單獨的 node.js 進程來觀測文件更改;
- 重新渲染更新的 React 組件的時間,通過直接在組件的 render 函數(shù)調(diào)用Date.now()來記錄。請注意,此調(diào)用發(fā)生在組件的虛擬 DOM render 階段,因此不會受到 React reconciliation 或?qū)嶋H DOM 更新的影響。
benchmark 還測量了兩種不同情況下的數(shù)字:
- “根”案例,該組件會導入 1,000 個不同的 child 組件,并且一起渲染。
- “葉子”案例,該組件是由根導入,但自身沒有子組件。
差別
在聊數(shù)字之前,有幾個額外的差異值得一提:
- Next 是否使用 React Server Component(RSC)。
- Vite 是否使用 SWC 來替代 Babel 進行 React 轉(zhuǎn)義。
React Server Components
Next 13 引入了一個主要的架構轉(zhuǎn)變,因為現(xiàn)在組件默認為服務器組件,除非用戶使用“use-client”指令明確選擇客戶端模式。不僅是默認設置,Next 文檔還建議用戶盡可能保持服務器組件模式,以提高終端用戶的性能。
我的初始 benchmark 測試測了 Next 13 在服務器模式下的根組件和葉組件的 HMR 性能。結果表明,在這兩種情況下,Next 13 的速度實際上都較慢,并且葉組件的差異顯著。
Round 1 snapshot (Next w/ RSC, Vite w/ Babel)[2]
當我在 Twitter 上發(fā)布這些數(shù)字時,很快就有人指出,我應該在沒有 RSC 的情況下對 Next 組件進行 benchmark 測試,以使其相等。所以我在 Next 根組件中添加了“useclient”指令,以選擇進入客戶端模式。事實上,在客戶端模式下,Next HMR 顯著提高,比 Vite 快 2 倍:
Round 2 snapshot (Next w/o RSC, Vite w/ Babel)[3]
SWC vs. Babel Transforms
我們的目標是使 benchmark 只關注 HMR 性能差異。為了確保我們確實在比較同一個東西,我們還應該消除另一個變量:Vite 的默認 React preset 使用 Babel 來轉(zhuǎn)換 React HMR 和 JSX。
React HMR 和 JSX 轉(zhuǎn)換不是與構建工具耦合的特性。可以通過 Babel(基于 js)或 SWC(基于 rust)完成。Esbuild 也可以轉(zhuǎn)換 JSX,但缺少對 HMR 的支持。
SWC 明顯快于 Babel(單線程下 20 倍,多核心下 70 倍)。Vite 目前默認為 Babel 的原因是在安裝大小和實用性之間進行權衡。SWC 的安裝容量相當大(node_modules 中占用 58MB,而 Vite 本身才 19MB),許多用戶仍然依賴 Babel 進行其他轉(zhuǎn)換,因此 Babel pass 對他們來說是不可避免的。當然,這在未來可能會改變。
Vite core 不依賴 Babel。只需要用 vite-plugin-swc-react-refresh[4] 來替換默認的 React 插件即可。切換后,我們看到了根案例中 Vite 的顯著改進,超過了 Next:
有趣的是,這里的成長曲線顯示,Next/turbo 在根情況下比葉情況下慢 4 倍,而 Vite 只慢 2.4 倍。這意味著 Vite HMR 在更大型的組件中表現(xiàn)更好。
此外,切換到 SWC 也應改善 Vercel benchmark 測試中 Vite 的冷啟動指標。
在不同的硬件上的性能
因為這是一個涉及 Node.js 和和原生 Rust 部分的復合測試,在不同的硬件上會有非凡的差異。我發(fā)布的結果是在我的 M1 MacBook Pro 上收集的。其他用戶在不同的硬件上運行了相同的 benchmark 測試,并報告了不同的結果。
在某些情況下,根案例下的 Vite 更快。
而在另外一些情況下,兩種情況下 Vite 都明顯更快。
Vercel 的澄清
在我發(fā)布了我的 benchmark 之后,Vercel 發(fā)布了一篇博文[5],澄清了他們的 benchmark 方法,并將其 benchmark 提供給公眾驗證。雖然這可能是第一天就做的事兒,但這絕對是朝著正確方向邁出的一步。
讀完帖子和 benchmark 代碼后,這里有幾個關鍵要點:
- Vite 實現(xiàn)仍然使用默認的基于 Babel 的 React 插件。
- 1k 組件的案例下有數(shù)字的四舍五入問題,Turbopack 的 15ms 被舍入城 0.01s,Vite 的 87ms 被舍入城 0.09s。這把本來接近 6 倍的差距擴大到了 10 倍。
- Vercel 的 benchmark 使用更新模塊的“瀏覽器 eval 時間”作為結束時間戳,而不是 React 組件重新渲染時間。
- 該帖子包括一張圖表,顯示當模塊總數(shù)超過 30k 時,Turbopack 可以比 Vite 快 10 倍。
總結下來,“比 Vite 快 10 倍”必須在以下條件下才成立:
- Vite 未使用相同的 SWC 轉(zhuǎn)換。
- 該應用程序包含超過30k個模塊
- Benchmark 只測量熱更新模塊被評估的時間,而不是實際應用更改的時間。
什么是“公平”比較?
由于 Vercel 的 benchmark 測試測量“模塊評估時間”,以排除 React 的 HMR 運行時引起的差異,我們可以假設 benchmark 測試的目標是對 Vite 和 Turbopack 固有的 HMR 機制進行公正的比較。
不幸的是,在這個前提下,Vite 仍然在 benchmark 測試中使用 Babel,這并不平等,這讓 10 倍速度的聲明無效了。在使用 SWC 轉(zhuǎn)換的 Vite 來矯正數(shù)字之前,應將其視為不準確的測試。
此外,我相信大多數(shù)人都會同意:
- 對于絕大多數(shù)用戶來說,30k 模塊數(shù)量是一個極不可能的場景。隨著 Vite 使用 SWC,達到 10 倍要求所需的模塊數(shù)量可能會變得更加不切實際。雖然這在理論上是可能的,但用它來證明 Vercel 一直營銷的成績,是很虛偽的。
- 用戶更關心端到端的 HMR 性能,即從保存到看到反映的更改的時間,而不是理論上的“模塊評估”時間。當看到“更新速度快 10 倍”時,一般用戶會考慮前者而不是后者。Vercel 在其營銷中圖方便省略了這一警告。實際上,Next 中服務器組件的端到端 HMR(默認值)比 Vite 中的慢。
作為 Vite 的作者,我很高興看到像 Vercel 這樣資金雄厚的公司在改進前端工具方面進行了大量投資。如果適用,我們甚至可以在未來在 Vite 中利用 Turbopack。我相信 OSS 領域的健康競爭最終會讓所有開發(fā)者受益。
然而,我也認為開放源碼軟件的競爭應該建立在公開溝通、公平比較和相互尊重的基礎上。令人失望和擔憂的是,看到激進的營銷使用了精心挑選的、未經(jīng)同行評審的、邊緣誤導性的數(shù)字,這些數(shù)字通常只在商業(yè)競爭中出現(xiàn)。作為一家建立在 OSS 成功之上的公司,我相信 Vercel 可以做得更好。