
2026/09/01 21:25
最速のWebAssemblyインタプリターの設計
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Wasmi 2.0 は、8 ヶ月の開発を経て正式にリリースされ、実行パフォーマンスに焦点を当てた根本的なエンジン改修を伴う主要なマイルストーンとなります。新しいバージョンは、Apple M2 Pro を含む多様なハードウェアでの幾何平均ベンチマークにおいて Wasmi 1.0 より約 2.2 倍高速動作し、かつ優れた起動速度を維持しています。この加速は、以下のような革新的なアーキテクチャ変更によって達成されています:スタック操作を削減するためのハードウェアベースの累積レジスタ(
ireg, freg32, freg64)の導入、より高速なアドレスベースアクセスを実現するために見直された InstanceEntity オブジェクト、ならびにメモリ肥大化を防ぐための 64 ビットセルの修正です。
このリリースには、いくつかの新機能が含まれています:バイナリアーティファクトのサイズを削減するための
validate クレート、安定した燃料計量、WebAssembly の確定型プロファイルへの対応、ならびに改善された CLI ツール。また、4 つの命令ディスパッチモードが利用可能となり(Direct-Threaded Code, Indirect-Threaded Code, Switch-Loop, および Call-Loop)、最適な構成を自動的に選択する auto-dispatch 機能も提供されます。CoreMark スコアを 50% 以上向上させたような重要な修正も含まれており、特に分岐予測を以前妨げていた Rust のデ最適化問題(具体的には DestinationPropagation)が解消されました。これらの進歩には、新しい CodeMap 構造によるロックフリーの同時アクセス機能も含まれ、Wasmi を Stitch や Wasm3 という競合ランタイムとの実行ギャップを縮めることを可能にしています。
今後を見据えると、Wasmi 3.0 では関数参照、例外処理、およびガベージコレクション(gc)などの欠落していた WebAssembly 3.0 プロポーザルの実装を行う予定です。2024 年 10 月以来、Stellar Development Foundation がスポンサーとしてプロジェクトを支援しており、パフォーマンスクリティカルなアプリケーション向けに堅牢な基盤の下で継続的に進化しています。
本文
Wasmi 2.0 リリース:エンジン刷新とパフォーマンス向上のすべて
Wasmi 1.0 の後続の投稿でお約束した通り、来期バージョン向けのエンジンの根本的な刷新を実施いたしました。未来はすでに到来しました!
Wasmi 1 は、高効率かつ機能豊富な WebAssembly (Wasm) 解釈器です。IoT デバイスやプラグインシステム(Typst, Zellij, Josh)、クラウドホスト、スマートコントラクト(Soroban, Ripple)、さらには軽量ゲームコンソール(Firefly Zero)などへの採用も検討されています。
まず、Wasmi プロジェクトを 2024 年 10 月以来スポンサーとして支援してくれたStellar Development Foundation (SDF) に心からの感謝を表します。また、記事の校正や多数の改善提案をいただいたFelix Kutzner 氏にも厚く御礼申し上げます。
Wasmi 2.0 のリリース
集中して作業を進めた 8 ヶ月を経て、Wasmi 2.0 が完成し、使用可能な状態となりました。主な特徴は以下の通りです。
- 実行パフォーマンスの向上:
- Apple M2 Pro を用いた
スイートの幾何平均において、Wasmi 1.0 より約 2.2 倍高速に動作します。wasmi-benchmarks
- Apple M2 Pro を用いた
- 新機能とサイズの削減:
- 新オプション(knobs)を導入。「validate」クレート機能などが含まれ、バイナリアーティファクトのサイズを大幅に削減できます。
- ユーザー要望への対応:
- ステーブルな燃料計測(stable fuel metering)。
- WebAssembly の決定論的プロファイルのサポート。
- 改良された Wasmi CLI ツールの導入。
リポジトリ・リリースノート・移行ガイド
詳細情報は以下のリンクから確認してください:
- リポジトリ
- リリースノート
- 移行ガイド: 1.0 → 2.0
- クレートドキュメント
GitHub でスターをいただければ幸いです!
Wasmi 2.0 の立ち位置
Wasmi 2.0 は最速のポータブル Wasm インタープリタの一つとして再定義されています。主なベンチマーク対象は以下の通りです。
- Wasm3
- WAMR fast-interpreter
- Wasmtime Pulley
- Makepad Stitch
- Wasmi 1.0
注: Wasmi 2.0 は上記のすべてのインタープリタに触発されて開発されました!
ベンチマークは
wasmi-benchmarks プロジェクトを使用して行われ、以下のハードウェア設定で可視化しています。
- Apple M2 Pro
- AMD EPYC 7763
- Intel Xeon Platinum 8370C
注: これは完全なベンチマーク結果の一部に過ぎませんが、全体像の概観には十分です。 起動性能については、Wasmtime Pulley が著しい外れ値となるため、対数スケールを使用する必要がある場合があります。
Wasmi 2.0 は実行パフォーマンスに焦点を当てていますが、起動パフォーマンスも以前と同じく突出しており、以前のバージョンとほぼ同等です。
結論:ベンチマーク結果
Wasmi 2.0 は明らかな**「最速のポータブル Wasm インタープリタ」**のカテゴリーに属すると結論づけることができます。
Wasmi 2.0 がなぜ这么快になるか (What made Wasmi 2.0 so fast?)
Wasm3 と Stitch は Wasmi 2.0 の裏側で多くの共通点を持っています。このセクションでは、Wasmi 2.0 に導入されたアイデアの詳細に加え、両者との類似点と意図的な違いについて論じます。
注: このセクションは Wasm およびインタープリターへの基本的な理解を前提としています。
新しい命令ディスパッチモード (New Modes of Instruction Dispatch)
Wasmi 2.0 では、命令ディスパッチの異なる 4 モードが用意されています:
| モード | 説明 | クレート機能 |
|---|---|---|
| Direct-Threaded Code | Wasm3 および Stitch で使用されている最も高速な構成です。関数ポインタを内部 IR に埋め込み、命令ハンドラから次へジャンプするためにテールコール(尾再帰)を使用します。 | (非利用) |
| Indirect-Threaded Code | Direct-Threaded コードと似ていますが、オペコード(OPCODE)を内部 IR に埋め込み、ジャンプテーブルを使用します。約 10-15% 遅いですが、メモリ使用量は著しく少なくなります。 | |
| Switch-Loop | Wasmi 1.0 で使用された技術です。ループと switch(または match)を使用する naive な方法で、特に Apple Silicon ではパフォーマンスが低下します。 | |
| Call-Loop | ループ内で次の命令ハンドラを呼び出します。テールコールを使用せず非常に遅いため推奨されません。単に構成行列から除外されます。 | |
Wasmi ユーザーへの推奨:
- Direct-Threaded Code: インタープリタのパフォーマンスを最大化したい場合(デフォルト推奨)。
- Indirect-Threaded Code: パフォーマンスとメモリ使用量のバランスが良い場合。
- Switch-Loop: テールコールをサポートしていないプラットフォームで実行する場合。
Wasmi 2.0 では、可能な限りスレッド型構成を自動的に使用する
auto-dispatch クレート機能も搭載しています。
命令ディスパッチモードのパフォーマンス
すべての命令ディスパッチモードは、裏側では同じインタープリタ実行ロジックとアーキテクチャを共有しています。詳細については Wasmi Dispatch Selection を参照してください。
注: Direct-Threaded Code の CoreMark 結果は上記と完全に一致していません(異なるラン・プロジェクトを使用)。
実行ハンドラシグネチャ (Execution Handler Signature)
Wasmi 2.0 では、すべての命令ハンドラが同じシグネチャを共有します:
fn( store: &mut PrunedStore, // `Store<T>` と関連付けられた実行への参照 ip: Ip, // 命令ポインタ(現在の場所とデコード・実行する命令) sp: Sp, // スタックの現在の関数内での位置 mem0: Mem0Ptr, // デフォルト線形メモリ `(memory 0)` のデータへのポインタ mem0_len: Mem0Len, // デフォルト線形メモリのバイト数 instance: Inst, // 現在実行されている関数で使用される Wasm インスタンスへのポインタ ireg: Ireg, // 整数および参照値のための蓄積レジスタ freg32: Freg32, // `f32` 値のための蓄積レジスタ freg64: Freg64, // `f64` 値のための蓄積レジスタ ) -> Done; // トラップまたは成功した停止を知らせる状態
各パラメータの役割
- store: 燃料計測、ホストコール、
およびmemory.grow
操作に使用されます。table.grow - ip: 実行がどの位置にあるかを示します。
- sp: 現在実行されている関数の値スタック内の位置です。
- mem0 / mem0_len: デフォルトメモリ (
) の最適化されたアクセスに使用されます。memory 0 - instance: グローバル、関数、テーブル、メモリなどの Wasm インスタンス関連のオブジェクトをロードするために使用されます。
- ireg, freg32, freg64: 命令間の中間結果を効率的に格納するための蓄積レジスタです。
問題:コール規約 (Calling Conventions)
Wasmi の命令ハンドラに必要な 9 つの引数のうち、7 つは一般目的レジスタ(GPR)で渡す必要があります。sysv64 などの一般的なコール規約では GPR は最大 6 つまでしか確保できないため、7 番目の整数引数にスピリングが必要になりパフォーマンス低下を招きます。
Wasmi 2.0 の解決策は、
instance 引数を浮動小数点レジスタに変換することです(比較的高価な操作のみで使用されるため)。ベンチマークにより、この移動は大きな問題ではないことが示されました。
注: 将来に不安定だが安定化する
ABI がより多くのプラットフォームで利用可能になる場合、この状況を改善できる可能性があります。preserve_none
蓄積レジスタ (Accumulator Registers)
Wasmi 2.0 は 3 つの新しい蓄積レジスタ(
ireg, freg32, freg64)を導入しました。これにより、実際のハードウェアレジスタから命令オペランドや結果を読み出し保存できます。コストの高いデコード、読み出し、または値の保存が不要になりました。
コピー命令 (Copy Instructions)
通常結果を蓄積レジスタに保存するため、古い設計が必要としなかったコピー命令が必要になります。
- 解決策 1:より効率的なコピー:
などの新しい命令でコピー数を減らします。u64_copy_sNr - 解決策 2:オペコードフュージョン: Wasm の直ちに続く
またはlocal.set
を単一の IR 命令で表現できるようにしました。local.tee
コントロールフロー境界を超えた蓄積レジスタ
Wasmi 2.0 は、可能な限り蓄積レジスタをコントロールフローの境界(ブロック、if フレーム、ループ)を超えて運びます。
- ブロックの結果タイプが
の場合、(i32 f32)
およびireg
を使用して結果を返します。freg32 - 結果が多い場合(例:
)、最後の結果のみを(i32, i32, i32)
に置き、他の結果はスタックスロットで返されます。ireg
注:
に同じ規則を適用する実験も行ったものの、パフォーマンス低下につながったためマージされませんでした。calls
インスタンスオブジェクトへのアクセス (Instance Object Access)
Wasmi 1.0 vs 2.0
- Wasmi 1.0: naive な方法を使用し、各タイプ(メモリー、テーブルなど)ごとにヒープ割り当てを行い、ストアからハンドルを取得する必要があるためコスト高です。
- Wasmi 2.0: 同じ Wasm モジュールのすべてのインスタンスが同じオブジェクトレイアウトを共有します。
は固定サイズのヘッダーと動的サイズのハンドルバッファからなり、アドレスは Wasm モジュールの属性として定義されます。InstanceEntity
これにより、スタックも再設計され、
StableArena タイプを使用することで、インスタンスオブジェクトへのアクセスはポインタオフセットのみで非常に高速になります。
比較:Wasm3 と Stitch
Wasm3 および Stitch は各インスタンスに独自のバイトコードを持たせますが、Wasmi はモジュール関連のバイトコードを使用するため、すべてのインスタンスが同じ IR を共有します。新しい設計により、Wasm3/Stitch のパフォーマンスレベルを達成しつつ、メモリ消費も大幅に改善しました。
ロックフリーコードマップ (Lock-Free CodeMap)
Wasmi 1.0
CodeMap 全体は Mutex で保護されたアリーナであり、すべての内部 Wasm コールでミューテックスを取得する必要がありました(非効率的)。
Wasmi 2.0
関数には単一のエンジンレベルアドレスがあり、Wasm3/Stitch に似てバイトコードにポインタを焼き付けます。
CodeMap は再割り当てを行わず、新しいモジュールの割り当てはシリアルで行われるため、実行時におけるアクセスはロックフリーです。
固定 64 ビットセル (Fixed 64-Bit Cells)
Wasmi 2.0 は常にセル幅を 64 ビットに固定します(SIMD 値は隣接する 2 セルを使用)。
- メモリ消費の増加を防ぎます。
- キャッシュ利用率の悪化を防ぎます。
- コール境界でのコピーオーバーヘッドを削減し、パフォーマンス向上につながります。
意図しない Rust のデオプティマイゼーション (Accidental Rust Deoptimization)
Stitch が CoreMark スコアで約 2200 ポイントから 3000 ポイントに回復したのは、Rust 1.92 でデフォルト有効化された MIR オプティマイゼーション
DestinationPropagation を無効化したためです。
Wasmi 2.0 も同様の問題を抱えており、修正により CoreMark スコアが ~2800 から 4200 超に上昇しました。これは単一の修正による約 50% の改善であり、最も重要な「最適化」の一つです。
次のステップ (What's Next)
Wasmi 3.0 では、以下の Wasm 提案をサポートすることを目標としています:
- 🚧 function-references
- 🚧 exception-handling
- 🚧 gc (Garbage Collection)
やってみましょう (Try It Out!)
さまざまな方法で Wasmi を試して使用してください:
- 📚
クレートをライブラリ依存関係として。wasmi - 🖥
またはリリースアーティファクトを使用して CLI アプリケーションとして。cargo install wasmi_cli - ⚙️ C-インターフェース言語で Wasmi C-API を使用して。
- 😈 ブラウザで Wasmi 2.0 でドームをプレイして。
- 📦 その主要なユーザーのいずれかを間接的に利用して享受して。
個人的なノート (Personal Note)
Stellar Development Foundation のスポンサーシップがなければ、Wasmi 2.0 は今日存在しませんでした。この資金により、私はオープンソースプロジェクトを 2 年間フルタイムで作業できました。
そのスポンサーシップは2026 年 10 月に終了します。Wasmi をそれ以降も続ける意図があり、次のスポンサーシップや開発の余地を残す役割を探しています。
もしあなたの会社で関心がある場合は、
robin.freyler@gmail.com までご連絡ください。