Vize

パフォーマンス

**⚠️ 進行中の作業:**Vize は積極的に開発中であり、まだ運用環境で使用する準備ができていません。ベンチマークの数値は開発ビルドによるものであり、変更される可能性があります。

Vize は、Rust のゼロコスト抽象化とネイティブ マルチスレッドを活用することで、標準の JavaScript ベースの Vue コンパイラと比較して大幅なパフォーマンスの向上を実現します。スピードはあればいいものではなく、開発者のエクスペリエンスの前提条件です。

ベンチマーク環境

このページには 2 つの測定環境が登場します。以下のすべての数値は、どちらで測定されたものかを明示しています。

リファレンスランナー。 ツール間の比較は Tool Benchmark ワークフローで測定され、 tools/benchmarks/results/tool-benchmark-latest.json にコミットされます。この成果物が引用可能な出典であり、 Blacksmith ベンチマーク スナップショット がその全体を公開しています。

マシン blacksmith-32vcpu-ubuntu-2404 (32 vCPU、AMD EPYC)
スナップショット コミット 1511788d96ea、2026-07-30
測定方法 ウォームアップ 1 回の後、計測 5 回の中央値
バージョン vize 0.303.0 · vue 3.6.0-beta.10 · Node v24.14.0

ローカルワークステーション。 以下のリンター、フォーマッタ、型チェッカーの表は、ローカルベンチ (tools/benchmarks/scripts/lint.tstools/benchmarks/scripts/fmt.tstools/benchmarks/scripts/check.ts) から手作業で維持されており、この環境で測定された ものです。リファレンスランナーではまだ再現できないため、参考値として読んでください。

マシン MacBook Pro (M2 Max、12 コア、96 GB RAM)
OS macOS 15.3.2 (Darwin 24.3.0)
Node.js v24.14.0
Vite v8.0.0 (Rolldown)
Vue v3.6.0-beta.10

ベンチマーク: 15,000 SFC ファイル

15,000 個の生成された Vue SFC ファイル(合計 58.7 MB)をリファレンスランナーでコンパイル:

@vue/compiler-sfc Vize 高速化
シングルスレッド 17.15s 3.95s 4.3x
全コア (32 vCPU) 6.08s 329.2ms 18.5x
compiler-sfc 1T 対 max 17.15s 329.2ms 52.1x

出典: コミット済みスナップショット tools/benchmarks/results/tool-benchmark-latest.jsoncompile サーフェス (run 30557718030) — README.mdBlacksmith ベンチマーク スナップショット が公開しているものと同じ成果物です。

シングルスレッドの改善は、Rust のゼロコスト抽象化 (GC なし、JIT ウォームアップなし、キャッシュに優しいメモリ レイアウト) から来ています。マルチスレッドの改善は、CPU コア数に応じてスケールする Rayon のワークスチール スレッド プールによるものです。

注意: このスナップショットは vize 0.303.0 時点のもので、「パフォーマンスのためのアーキテクチャの選択」で説明するアリーナと式の作業が入る前のものです。日付が記録されており再現可能ですが、現在のツリーの測定値ではありません。ツール間サーフェスをリファレンスランナーで再記録する作業は保留中です。

なぜ Rust なのか?

ゼロコストの抽象化

Rust の所有権モデルにより、ガベージ コレクションの一時停止が不要になります。テンプレートの AST ノードはコンパイルごとのアリーナ (vize_carton) に置かれ、テキストはテンプレートのソースから借用します。つまりノードは純粋なデータであり、それ自身が所有するヒープ割り当てを持ちません (crates/vize_relief/src/relief/elements.rs)。これはつまり:

  • GC 一時停止なし — V8 ベースのコンパイラでは、ガベージ コレクションにより予測できない遅延のスパイクが発生する可能性があります。Vize には GC オーバーヘッドがありません。
  • JIT ウォームアップなし — V8 の JIT コンパイラーはホット パスを最適化するのに時間がかかります。Vize は最初の命令から全速力で実行されます。
  • 予測可能なパフォーマンス — Rust の事前コンパイルは、V8 の最適化ヒューリスティックに依存せず、実行間でパフォーマンスが一貫していることを意味します。

ネイティブ マルチスレッド

Vize はデータ並列コンパイルに Rayon を使用します。各 SFC ファイルは個別にコンパイルされるためワークロードは自明に並列であり、crates/vize/src/commands/build/runner.rs のバッチドライバーが入力をスレッドプールに分散します:

// crates/vize/src/commands/build/runner.rs — バッチドライバー
planned_inputs
    .par_iter()
    .map(|input| compile_file_with_profile(&input.source, compile_settings, &stats))
    .collect()

アリーナはここでは作られません。アリーナが生まれる場所 — vize_atelier_sfc 内のテンプレート、スクリプト、スタイルの各入口 — でワーカーごとのプールから取得されます:

// 例: crates/vize_atelier_sfc/src/compile.rs
let allocator = vize_carton::pool::acquire();

ワークスチール方式とは、あるファイルが他のファイルよりも大幅に大きい場合、アイドル状態のスレッドがビジースレッドのキューから作業を奪い、ほぼ完璧な負荷分散を維持することを意味します。

効率的なメモリレイアウト

Rust の構造体レイアウトと列挙型判別子はコンパクトです。vize_relief の AST 表現はキャッシュに適しており、メモリ帯域幅のボトルネックを軽減します:

  • 1 バイトの判別子NodeType は 27 個のバリアントを持つ #[repr(u8)] です (crates/vize_relief/src/relief/core.rs)。ノードの種別はヒープに割り当てられた文字列ではなく 1 バイトで済みます。
  • 固定されたノードサイズ — すべてのテンプレートノードにサイズの const アサーションが付いており、ノードを大きくするフィールドは予算ではなくビルドを失敗させます。ElementNode は 104 バイト、SimpleExpressionNode は 88、AttributeNode は 56、TextNode は 24、SourceLocation は 8 です (crates/vize_relief/src/relief/{elements,expressions,control_flow,nodes}.rs)。
  • オブジェクト ヘッダーなし — JavaScript オブジェクト (プロトタイプ チェーン、プロパティ マップ、隠しクラス ポインターを持つ) とは異なり、Rust の構造体はオーバーヘッドのない純粋なデータです。

実行時のオーバーヘッドなし

V8 で実行される JavaScript ベースのコンパイラーとは異なり、Vize はネイティブ コードに直接コンパイルします。JIT ウォームアップ、ガベージ コレクター、イベント ループ競合はありません。CLI はプラットフォームごとに自己完結型のネイティブ実行可能ファイルとして配布されます。musl Linux ターゲットでは完全に静的リンクされており、CI がそれを検証しています (tools/commands/ci/github/verify-musl-cli-binary.rs)。glibc、macOS、Windows の各ターゲットでは、システムの C ライブラリに動的リンクされます。Vite プラグインは、同じコンパイラを別プロセスではなくネイティブの Node アドオン (@vizejs/native) として読み込みます。

パフォーマンスのためのアーキテクチャの選択

アリーナの割り当て

vize_carton::Allocator は AST ノード用のバンプ アロケーターであり、oxc_allocator をラップすることで、テンプレートノードと保持された JavaScript 式が 1 つのアリーナと 1 つのライフタイムを共有します (crates/vize_carton/src/allocator.rs)。これはつまり:

  • 割り当ては O(1) — ポインタを前方に移動するだけです。フリーリストのトラバーサルや断片化の管理はありません。
  • 回収は O(1) で再利用される — コンパイルの終わりにアリーナは破棄されるのではなく reset() されます。バンプ ポインタはチャンクの先頭に戻り、アリーナはワーカーごとのフリーリストに返されます (crates/vize_carton/src/pool.rs、ワーカーあたり最大 4 個のアイドル アリーナ)。次のファイルは OS に追加のメモリを要求せず、同じメモリを再利用します。
  • メモリの局所性が優れている — ノードがメモリ内に連続してパックされ、ツリー トラバーサル中の L1/L2 キャッシュ ヒットが最大化されます。

アリーナに置かれた値は、そのコンパイルより長く生存できません。この契約はコンパイラによって強制され (reset&mut self を取り、プールのガードは自身のアリーナを所有します)、デバッグビルドではさらに世代スタンプが、アリーナが再利用された後に値が読まれた場合にパニックします (crates/vize_carton/src/allocator/generation.rs)。

AST には Drop を実装した型が 1 つもありません。アリーナのコンテナ型はドロップが必要なペイロードを拒否するため、これは慣習ではなくコンパイルエラーになります。

シングルパス トークナイザー

vize_armature のトークナイザーは &[u8] 上のバイト指向ステートマシンです (crates/vize_armature/src/tokenizer.rs)。トークンを実体化することは一切ありません。コンパイラのどこにも Token 型もトークンのベクタも存在しません。代わりに tokenize() が入力の末尾まで 1 回走査し、パーサーが実装する Callbacks シンクにイベントを送ります。各イベントは生成されたその場で同期的に処理され、2 フェーズ設計が必要とする中間配列はそもそも存在しません。

これはプッシュ型であり、遅延プル型ではない点に注意してください。パーサーはトークンを要求しませんし、ループを途中で止めることもできません。

文字列インターン

1 回のコンパイル内で繰り返し現れる名前 — 正規化されたディレクティブ名、アセット名、キャメルケース化された引数名 — は vize_carton::interner によってアリーナ上のアトムにインターンされます。コンパイル時の phf セットには 181 個の既知の名前 (HTML/SVG/MathML のタグ、Vue の組み込みコンポーネント、ディレクティブ名、トランスフォームが特別扱いする属性) が入っており、これらはアリーナに一切触れずに 'static リテラルへ解決されます。これはつまり:

  • 繰り返される計算済みの名前は、1 つのアリーナ割り当てを共有します
  • 既知の名前の探索はコンパイル時の完全ハッシュであり、割り当てを伴いません

インターンは一般的な経路ではなくフォールバックです。ほとんどの名前はそもそもコピーされません。タグ名、属性名、そして式の内容の多くは、テンプレートのソースから直接借用した &'a str スライスであり、通常の経路では何も割り当てません (フィールドごとの方針は crates/vize_carton/src/interner.rs に記載されています)。

アトムは通常の &'a str であるため、名前の比較はポインタの同一性ではなく内容の比較です。インターンがもたらすのは割り当ての削減とキャッシュ局所性であり、== の高速化ではありません。

インクリメンタルコンパイル

Vite プラグイン (@vizejs/vite-plugin) はファイル単位でキャッシュしますが、キーの異なる 2 つの層があります:

  • メモリ内、開発と HMR のため — 解決済みのファイルパスをキーとします (npm/builder/vite/src/plugin/compiled-module-cache.ts)。エントリはキーを付け直すのではなくホットアップデート時に明示的に破棄されるため、変更されたファイルだけが再コンパイルされ、その隣接ファイルは再コンパイルされません。
  • プリコンパイルの変更検出mtime とサイズをキーとし、比較は Rust 側で行われます (crates/vize_atelier_sfc/src/vite_plugin/precompile.rs)。バッチが再コンパイルするファイルを決めるのはこのゲートです。
  • ディスク上、プロセスをまたいでnode_modules/.vize/vite-precompile に置かれ、ソースの SHA-256 ハッシュに加えて、コンパイラ バイナリの同一性と解決済みオプションを含むマニフェストキーをキーとします (npm/builder/vite/src/plugin/precompile-cache-key.ts)。ここで内容ハッシュを使うのは、mtime がマシンやチェックアウトをまたいで信頼できないからです。

実測: アリーナと式の作業

上記のコンパイラ内部の作業は、クレートごとのマイクロベンチ ハーネス (cargo bench --bench davinci) によって、固定された 6 つのフィクスチャのラダー tools/benchmarks/crates/davinci_harness/fixtures/{small,medium,large,stress-deep,stress-wide,stress-interp}.vue 上で測定されます。

これらの数値の読み方。 割り当て回数は決定的でマシンに依存しないため、正確な事実であり、リグレッションのラチェットとして使われます。実行時間は共有の開発マシン上で --quick サンプリングにより測定されたもので、方向性を示すだけです。リファレンスランナー (Blacksmith) での記録は保留中であり、そのため davinci-road/plan/budgets.tomlwall_p50_nsallocs はすべて 0 (「未記録、参考のみ」の意味) のままです。実行ごとの結果ファイルは tools/benchmarks/results/davinci/ に出力されますが、これはローカルの成果物であり、コミットされたベースラインではありません。

コンパイル 1 回あたりの割り当て呼び出し回数、文字列とアリーナの作業の前後 (正確値、同一フィクスチャ):

フィクスチャ パース DOM コンパイル SSR コンパイル Vapor コンパイル
small 21 → 9 52 → 39 73 → 60 90 → 73
medium 171 → 107 329 → 264 1,099 → 1,030 588 → 515
large 350 → 272 656 → 573 1,106 → 983 1,136 → 1,003
stress-deep 397 → 155 669 → 426 612 → 369 764 → 514
stress-wide 213 → 204 255 → 245 416 → 405 280 → 261
stress-interp 616 → 105 1,048 → 536 3,149 → 2,637 1,495 → 974

ノードのサイズもそれに伴って縮小し、新しいサイズは const アサーションで固定されています。RootNode 296 → 224 バイト、DirectiveNode 208 → 176、ElementNode 128 → 104、SimpleExpressionNode 120 → 88、AttributeNode 80 → 56、TextNode 32 → 24。

ピーク常駐メモリ。 ファイルをまたいだアリーナの再利用は単一で最大の成果であり、速度ではなくメモリの結果です。コミット済みコーパスの 36,541 個の SFC すべてをコンパイルした場合 (vize build "tests/_fixtures/_git/**/*.vue" --format statsci-opt バイナリ、/usr/bin/time -l の最大常駐セットサイズ、前後で同一マシン):

ワーカー数 変更前 変更後 変化 各実行回数
12 766.5 MB 171.1 MB −77.7% 5
1 717.0 MB 88.2 MB −87.7% 3

ワーカー 1 個の数値は蓄積のシグナルです。スケジューリングの影響を受けないため、以前のピークがワーカーごとのアリーナではなくファイルごとのリークだったことを示しています。実行時間はノイズの範囲内で変化なく、出力された 36,541 ファイルはすべてバイト単位で同一でした (SHA-256 マニフェストを比較)。

式の再パース。 テンプレートの式はテンプレート解析時に一度だけ解析され、ノードに保持されるようになりました。コンシューマーはテキストを再解析する代わりに、保持された AST を読みます。SSR レーンでは stress-interp フィクスチャのコンパイル 1 回あたりの冗長な式の再解析が 500 回からゼロになり、その融合レーンは保持導入前のツリーに対して正味 −13.6% の実行時間となりました (346.8µs → 299.8µs)。解析のコストは増えましたが、コンシューマーのコストはそれ以上に減っています。DOM と Vapor のレーンにはそのフィクスチャで削除できる再解析がなかったため、増えた解析コストを依然として負担しています。その解消は、出荷済みの成果ではなく、残るフェーズ作業として追跡されています。

ベンチマーク: Linter — Patina vs eslint-plugin-vue

15,000 個の Vue SFC ファイルのリンティング (ローカルワークステーション):

eslint-プラグイン-vue (ST) Vize Patina (ST) スピードアップ eslint-プラグイン-vue (MT) Vize Patina (MT) スピードアップ eslint ST 対 Vize MT
時間 45.08秒 4.02秒 11.2x 16.38秒 784ミリ秒 20.9x 57.5x

再現するには、vp run --workspace-root bench:lint を実行します。

タイプ認識型 lint プロファイル

タイプ認識リンティングは、コストが集中する傾向があるフェーズ (SFC 解析、 クロッキー分析、仮想 TypeScript 生成、テンプレート クエリ コレクション、および Corsa プローブ。いつ 複数のテンプレートに基づく型認識ルールが有効になっており、Patina はテンプレート式を収集し、 テンプレート Corsa プローブ フェーズの前の 1 つの AST ウォークでの Promise クエリ。クエリコレクションも共有 OXC 式は安全でないテンプレートとフローティング Promise チェックを解析するため、1 つのテンプレート式 両方のルールが有効な場合、重複した解析コストは発生しません。

vize lint --profile --preset opinionated src を実行して、ローカル プロジェクト内のこれらの行を表示します。の プロファイル レポートには、経過時間カバレッジ、累積的なデータをチェックする厳密な監査セクションも含まれています。 ホットファイルと内部をリストする前に、ワーカー時間、低速しきい値ヒット、キャプチャされた内部スパンを確認します。 操作。ホットファイル行はステージごとのシェアとスループットを示し、操作行は支配的なフラグを示します スパンまたは最大/平均スパイク。

ベンチマーク: フォーマッタ — グリフと Prettier

15,000 個の Vue SFC ファイルのフォーマット (ローカルワークステーション):

プリティア (CLI) Vize Glyph (ST) スピードアップ Vize Glyph (MT) Prettier CLI と Vize MT
時間 101.20秒 2.97秒 34.1x 835ミリ秒 121.2x

再現するには、vp run --workspace-root bench:fmt を実行します。

ベンチマーク: 型チェッカー — canon 対 vue-tsc

現在の Corsa-backed 診断パスを使用した500 個の生成された Vue SFC ファイルのタイプ チェック (ローカルワークステーション):

vue-tsc (ST) Vize Canon (ST) スピードアップ vue-tsc (MT) Vize Canon (MT) スピードアップ vue-tsc ST と Vize MT
時間 4.38秒 511ミリ秒 n/a (cross-engine) 4.41秒 493ミリ秒 n/a (cross-engine) n/a (cross-engine)
料金 114 ファイル/秒 979 ファイル/秒 113 ファイル/秒 1.0k ファイル/秒

タイプチェックの行は 2 つの TypeScript エンジンにまたがります。vue-tsc は JavaScript コンパイラを、Vize check はネイティブ tsgo (Corsa) を実行するため、単一の倍率は公開せず (n/a (cross-engine))、エンジンクラスごとに順位付けします。単一の数値は TypeScript の Go 書き直しを Vue レイヤーの成果として誤って伝えてしまいます。両方の計測値は実測であり、同じ実行で得られたものです。エンジンクラスごとの順位は Blacksmith ベンチマーク スナップショット を参照してください。

**注意:**Vize canon はまだ開発初期段階にあり、Corsa を利用した診断パスは vue-tsc の忠実度にまだ追いついていません。これらの測定値は、プロジェクト セッション フォールバックを備えた現在の CLI ファースト ネイティブ実装を反映しており、診断カバレッジとパリティが向上するにつれて変化します。

この簡単なベンチマークを再現するには、cargo build --release -p vize の後に node tools/benchmarks/scripts/check.ts 500 を実行します。

タイプチェッカープロファイル

500-SFC プロファイル フィクスチャは、Corsa CLI コマンド内でのほとんどの所要時間を維持しますが、インポート リライト高速パスにより、Vue 指定子のないファイルに対する以前の OXC 解析コストが削除されます。

メトリック 現在
canon.import.rewrite.vue 26.77ミリ秒 2.45ミリ秒
生成された最大の仮想 TS 15,401B 14,414B
プロファイルの合計経過時間 1.88秒 668ミリ秒
Corsa 診断フェーズ 1.67秒 482ミリ秒
Corsa CLI 解析 該当なし 10.41ミリ秒

Rust 側の virtual project フェーズ — ファイルごとの SFC 解析、クロッキー解析、 仮想 TS の生成とインポートの書き換え — rayon のスレッド全体で扇動されます VirtualProject::register_paths内のプール。各 .vue ファイルは独立しています ワークスペース オプションが解決されると、単一のバッチが並列化されます。 きれいに。 1,000-SFC フィクスチャでは、位相が約 71 ms から約 25 ms に低下します。 コルサも発動する。

診断を多用する e2e フィクスチャ

tools/benchmarks/scripts/check.ts は、フィクスチャが存在する場合、tests/_fixtures/_git/npmx.dev アプリも測定します。これにより、実際のアプリケーション フィクスチャ上の診断マッピング パスが取得されます。

治具 ソース SFC ファイル 仮想ファイル 診断 Vize Canon
npmx.dev アプリ 134 226 1,053 1.94秒

このフィクスチャの現在のプロファイルでは、CLI 診断解析が約 7 ミリ秒に維持されます。現在、ほとんどの時間は Corsa CLI コマンド自体に費やされています。フレームワークの自動インポート スタブを 1 つのアンビエント ファイルにホイストすると、生成される最大の仮想 TS ファイルも約 275 KB から 144 KB に削減されました。

ベンチマーク: Vite プラグイン — @vizejs/vite-plugin 対 @vitejs/plugin-vue

  • *1,000 個の Vue SFC インポート**を含む Vite ビルド (すべて 1 つのエントリでインポート)。Blacksmith blacksmith-32vcpu-ubuntu-2404 上で計測、5 回の実行の中央値:
@vitejs/plugin-vue @vizejs/vite-plugin スピードアップ
ビルド時間 1.71s 631.7ms 2.7x

注: @vizejs/vite-plugin は Vue SFC コンパイル手順のみを置き換えます。パフォーマンスの違いは完全にその部分から生じます。依存関係の解決、モジュール グラフの構築、バンドル (ロールダウン)、およびその他すべての Vite 内部は @vitejs/plugin-vue と同一です。純粋なコンパイルのパフォーマンスについては、上記の コンパイラ ベンチマーク を参照してください。 @vizejs/vite-plugin は、ネイティブ マルチスレッド コンパイルを使用して .vue ファイルを積極的にプリコンパイルします。これにより、より高速な HMR も可能になります。

この行はコミット済みスナップショット tools/benchmarks/results/tool-benchmark-latest.jsonvite サーフェス (run 30557718030) です。README.mdBlacksmith ベンチマーク スナップショット が公開しているものと同じ成果物であり、tests/tooling/docs-vite-benchmark-row.test.ts が全ロケールでこの成果物に固定しています。

それまでここに掲載していた 957ms / 479ms / 2.0x は、#3392 以前の tools/benchmarks/scripts/vite.ts によるものでした。このハーネスは、ウォームアップが残した永続プリコンパイル キャッシュ付きの Vize と、ゼロからコンパイルする @vitejs/plugin-vue を比較していました。現在このハーネスは実行マシン上でコールドとウォームを別々に報告するため、その出力はローカルな診断値であり、公開できる速度比ではありません。vp run --workspace-root bench:vite は変更前後の自己比較に使ってください。