経緯
VercelがNext13と共にTurbopackを公開しました。このTurbopack、Webpackの後継として登場し、Webpackの700倍速く、Viteの10倍速く動作するとのことです。しかし、その秒数はTurbopackが0.01秒、viteが0.09秒と書かれており、10倍と表現するにはやや誇張された値です。このマーケティングに不信感を抱いたVueとViteの開発者、Evan Youは自身で実際にTurbopackの性能とViteの性能を比較をされました。注意としてEvan YouはTurbopackを否定したいのではなく、健全なOSSコミュニティでの競争を望んでいるそうです。
以下はそのGoogle翻訳ベースの翻訳です。間違いも多分にあるでしょうから、詳細が気になる方は上のリンクよりご確認ください。
翻訳
VercelはRust製のWebpack、Turbopackの案内を出しました。
発表の見出しの一つは、Turbopackが「Viteよりも10倍速い」というものでした。この1文はツイート、ブログ投稿、Vercelユーザーに送信されるマーケティングメールなど、Vercelの様々なマーケティング資料で繰り返されています。
ベンチマークグラフはTurbopackのドキュメントにも含まれており、当初はTurbopackを使用したNext 13がReact Hot-Module Replacement(HMR)1を0.01秒で実行できるのに対し、viteでは0.09秒かかることを示しています。コールドスタート(0からの起動)パフォーマンスのベンチマークもありましたが、コールドスタートベンチマークのいずれも10倍の差を示さなかったため、「10倍速い」という主張はHMRパフォーマンスに対して述べられているとしか考えられません。
Vercelは、これらの数値を作成するために使用したベンチマークへのリンクをマーケティング資料やドキュメントに含めませんでした。そこで私は好奇心をそそられ、新しくリリースされたNext13とVite3.2を使用したベンチマークで自分自身の主張を検証することにしました。コードと方法論はここで公開されています。
私の方法論の要点は、次の二つのタイムスタンプ間のデルタを測定することにより、HMRパフォーマンスを比較することです。
- ソースファイルが変更された時刻。ファイルの変更を監視する別のNode.jsプロセスによって記録されます。
- 更新されたReactコンポーネントが再レンダリングされ、
Date.now()
をコンポーネントのレンダリング出力で直接呼び出すことによって記録された時間。子の呼び出しはコンポーネントの仮想DOMレンダリングフェーズ中に発生するため、Reactの調整や実際のDOMの更新の影響を 受けないことに注意してください。
このベンチマークでは、次の二つの異なるケースでも数値が測定されます。
- コンポーネントが1000の異なる子コンポーネントをインポートし、それらをまとめてレンダリングする「ルート」ケース。
- コンポーネントが根によってインポートされますが、独自のインポートまたは子コンポーネントを持たない「リーフ」ケース
ニュアンス
数値に飛ぶ前に、いくつかのニュアンスについて補足しておきます。
- NextがReact Server Component(RSC)を利用しているかどうか
- ViteがReact変換にBabelの代わりにSWCを使用しているかどうか。
React Server Component
'use client'
はNext13では、ユーザーがディレクティブで明示的にクライアントモードにオプトインしない限り、コンポーネントがデフォルトでサーバーコンポーネント2になるという大きなアーキテクチャの変更が導入されました。これがデフォルトであるだけでなく、Nextのドキュメントではエンドユーザーのパフォーマンスを向上させるために、可能な限りサーバーモードを維持することをユーザーに推奨しています。
私の最初のベンチマークでは、サーバーモードでルートコンポーネントとリーフコンポーネントの両方を利用して、Next13 のHMRパフォーマンスを計測しました。結果は、どちらもNext13のほうが遅く、その差はリーフコンポーネントで顕著に示されました。
ラウンド1スナップショット(RSCあり、viteはbableで実行)
数値をTwitterに投稿したところ、RSCを使用せずにNextコンポーネントを同じように測定する必要があることを指摘されました。そのため、次はルートコンポーネントに直接"use client"
を追記し、クライアントモードにオプトインしました。実際、クライアントモードではNextのHMRは大幅に改善され、二倍速くなりました。
ラウンド2スナップショット(RSCなし、viteはbabelで実行)
SWCとBabel変換
私たちの目標は、ベンチマークをHMRパフォーマンスの違いのみに集中させることです。リンゴとリンゴを比較していることを確認するには、別の変数の排除する必要があります。それは、ViteでデフォルトのReactプリセットがBabelを使用して、React HMRとJSXを変換するということです。
React HMRおよびJSX変換は、ビルドツールに結合された機能ではありません。これは、babel(jsベース)または、SWC(rustベース)のいずれかを介して実行できます。ESBuildはJSX変換もできますが、HMRはサポートされていません。SWCはBabelよりも大幅に高速です。(シングルスレッドで20倍、マルチコアで70倍)。現在ViteがデフォルトでBabelを利用している理由はインストールサイズと実用性のトレードオフです。SWCのインストールサイズは非常に大きく(node_modulesでvite自体が19MBにたいして58MB)、多くのユーザーは依然としてほかの変換をBabelに依存しているため、Babelの利用は避けられませんでした。ただし、これは将来変更される可能性があります。
さらに重要なことは、Turboが示した比較のWebpack実装もSWCを使用していることです。
ViteのコアはBabelに依存しません。React変換を処理するためにBabelの代わりにSWCを使用する場合、Vite自体を変更する必要はありません。デフォルトのReactプラグインをvite-plugin-swc-react-refreshに置き換えるだけです。切り替え後は、ルートケースでのViteに大幅な改善が見られ、Nextに追いつきました。
Vite(ルート) | Vite(リーフ) | Next(ルート) | Next(リーフ) | |
---|---|---|---|---|
5回の平均実行時間(ms) | 338.2 | 141.8 | 334.6 | 84.4 |
興味深いことに成長曲線は、Next/Turboがリーフケースと比べ、ルートケースが約4倍遅くなったのに対し、viteは2.4倍遅くなったことを示しています。これは、Vite HMRがさらに大きなコンポーネントでより適切にスケーリングされる曲線を意味します。
さらに、SWCに切り替えると、VercelのベンチマークにおけるViteのコールドスタートの結果も改善されるはずです。
異なるハードウェアでのパフォーマンス
これはNode.jsとネイティブのRustパーツ両方を含むベンチマークであるため、ハードウェアごとに自明でない差異があります。私が投降した計測数値は、すべてM1 MacBook Proで収集されました。ほかのユーザーは異なるハードウェアで同じベンチマークを実行し、異なる結果を報告しています。ルートケースでViteのほうが高速の場合もあれば、両方Viteのほうが大幅に高速な場合もあります。
Vercelの説明
私がベンチマークを投稿した後、Vercelは計測の方法論を明確にするブログ投稿を公開し、ベンチマークを公開検証できるようにしました。これは初日に行うべきでしたが、正しい方向への第一歩であることは間違いありません。
投稿とベンチマークを読んだ後、いくつかの重要なポイントをいかに示します。
-
Viteの実装は引き続きBabelベースのReactプラグインを使用していますが 、同じベンチマークのTurbopackとWebpackの実装 はの両方がSWCを使用しています。これだけでもベンチマークは根本的に不公平な比較になります。
-
1kコンポーネントの場合元の数値の丸めには問題がありました。Turbopackの15msは0.01秒に切り捨てられ、viteの87msは0.09秒に切り上げられました。これは元の数値が6倍近くだったのに、10倍の差として公表されました。
-
VercelのベンチマークはReactコンポーネントを再レンダリングする時間ではなく、更新されたモジュールの「ブラウザ評価時間」を終了のタイムスタンプとして使用しています。前者は論理的な時間ですが、後者はユーザーが認識したエンドツーエンドのHMR更新速度を反映しています。また、viteの評価と更新のタイムスタンプを収集する方法に欠陥があることを発見しました。(最後を参照)
-
投稿によると、合計モジュール数が30kを超えるとTurbopackがviteの10倍高速になる可能性が示すグラフが含まれています。ただし、どちらのツールも実際には最大10000モジュールまで常に拡張されています。Vite HMR曲線が上昇し始めるのはモジュール数が20kを超えた時だけです。Viteアプリが依存関係を事前にバンドルしていることを考えると、20k以上のsrcモジュールを含むプロジェクトは現実にはほとんどありません。10倍の主張を正当化するために、30kという数値を使用するには、チェリーピッキングのように感じます。
要約すると、「Viteより10倍高速」という主張は、以下の要件が当てはまる場合のみ有効です。
- ViteはNextと同じSWC変換を使用していません。
- アプリケーションには30000を超えるモジュールが含まれています。
- ベンチマークは、ホットアップデートされたモジュールが評価される時間のみを測定し、変更が実際に適用される時間は測定されません。
「公平」な比較とは何ですか?
比較しようとしているものが「すぐに使えるデフォルト」である場合、Nextで有効になっているRSCと比較する必要があります。これがデフォルトであり、Nextユーザーに使用を積極的に推奨されているものだからです。VercelのベンチマークはRSCを使用しておらず、「モジュール評価時間」を測定して、ReactのHMRランタイムに起因する差異を除外しているため、ベンチマークの目標はViteとTurbopackの固有のメカニズムを同じもの同士に対してHMRを実行することで、比較しようとしていると想定することが妥当です。
その前提で、残念ながら、ViteがまだベンチマークでBabelを使用している事実は、同等のシナリオではなく、以前として10倍の主張を無効にしたままにしています。SWC変換を使用して、Viteで数値が更新されるまで、これは不正確であるとみなされます。
さらに、次のことにほとんどの人は同意してくれると思います。
- 30kのモジュールは大多数のユーザーにとってありえなさそうなシナリオです。SWCを使用するViteでは、10倍の主張に到達するために必要なモジュール数は、さらに非現実になる可能性があります。理論的には可能ですが、Vervelが推し進めている類のマーケティングを正当化するためにモジュールを増やすことは不誠実に思えます。
- ユーザーは理論的な「モジュール評価」のタイミングとして、エンドツーエンドのHMRパフォーマンス、つまり、保存から変更が更新されるまでの時間に関心があります。「10倍速いアップデート」を見ると、多くのユーザーは後者ではなく、前者の観点で考えるでしょう。Vercelは、マーケティングでこの警告を都合よく省略しました。じっさいには、Nextのサーバーコンポーネント(デフォルト)のエンドツーエンドのHMRはViteよりも[遅くなります]
Viteの作成者として、Vercelのような資金力のある企業がフロントエンドツール改善に多額の投資を行っていることをうれしく思います。将来的には、必要に応じて、ViteでTurbopackを活用する可能性もあります。OSSでの健全な競争は将来的にすべての開発者に利益をもたらすと私は信じてきます。
しかし、OSSの競争は、オープンなコミュニケーション、公正な比較、相互尊重に基づくべきと私は信じています。通常、商業的な競争でみられる、有利な結果だけピックされ、査読されてない、誤解を招くような数値を使用した積極的なマーケティングをみるのは残念であり、心配です。OSSの成功の上で構築された会社として、Vercelはもっとうまくやれると信じています。