プログラミング
最速のWebAssemblyインタープリタのエンジニアリング
Engineering of the fastest WebAssembly interpreters (wasmi-labs.github.io)
要約
Wasmi 2.0は、前バージョンと比較して実行速度が約2.2倍向上した新しいWebAssemblyインタープリタです。この高速化は、直接スレッドコードなどの新しい命令ディスパッチモードの導入や、実行ハンドラシグネチャの最適化によって達成されました。Wasmi 2.0は、様々なハードウェア環境で他の主要なインタープリタと比較しても最速クラスの性能を示しており、IoTデバイスからクラウドホストまで幅広い用途に適しています。
全文翻訳
前回のWasmi 1.0に関する投稿で、将来のWasmiバージョンでは根本的なエンジンオーバーホールを約束しました。その未来は今です!Wasmiは効率的で機能豊富なWebAssembly(Wasm)インタープリタです。IoTデバイス、プラグインシステム(Typst、Zellij、Josh)、クラウドホスト、スマートコントラクト(Soroban、Ripple)、さらには軽量ゲームコンソール(Firefly Zero)にも最適です。
詳細に入る前に、2024年10月からWasmiプロジェクトをスポンサーしてくれているStellar Development Foundation(SDF)に多大な感謝を申し上げます。彼らのスポンサーシップなしでは、Wasmiプロジェクトは今日の状態にはなれなかったでしょう。また、記事の校正を行い、多くの改善点を提案してくれたFelix Kutznerにも特別に感謝します。
Wasmi 2.0リリース
本日、8ヶ月間の集中的な作業を経て、Wasmi 2.0が完成し、使用可能になったことを発表できることを嬉しく思います。
このリリースは実行パフォーマンスに焦点を当てています:Wasmi 2.0は、Apple M2 Pro上のwasmi-benchmarksスイート全体での幾何平均で、Wasmi 1.0よりも約2.2倍高速に実行されます。
Wasmi 2.0は、validate crateフィーチャーのような新しいノブも搭載しており、バイナリ成果物のサイズを大幅に削減します。ユーザーから要望のあった機能、例えば安定した燃料計測、WebAssemblyの決定論的プロファイルへの対応、そして改善されたWasmi CLIツールもこのリリースに含まれています。
リポジトリ
リリースノート
移行ガイド:1.0 → 2.0
クレート
ドキュメンテーション
GitHubでスターをいただけると、いつでも私を幸せにできます!
Wasmi 2.0の着地点
Wasmi 1.0よりも2.2倍高速というのは素晴らしいですが、Wasmi 2.0は競合他社と比べてどうでしょうか?
そのため、いくつかの最速のポータブルWasmインタープリタと比較してベンチマークを行いました:
Wasm3
WAMR fast-interpreter
Wasmtime Pulley
Makepad Stitch
Wasmi 1.0
注:Wasmi 2.0は、上記のすべてのインタープリタからインスピレーションを得ています!
ベンチマークはwasmi-benchmarksプロジェクトを使用して実施され、ご自身のマシンで簡単に再現できるはずです。
インタープリタの好みを可視化するために、3つの異なるハードウェアセットアップでベンチマークを実行しました:
Apple M2 Pro
AMD EPYC 7763
Intel Xeon Platinum 8370C
注:これはwasmi-benchmarksプロジェクトがサポートする全ベンチマークとランタイムのほんの一部ですが、良い概要を提供しています。
幾何平均
以下の2つのプロットは、上記のWasmランタイムにおけるwasmi-benchmarksスイートの全実行ベンチマークと全起動ベンチマークの幾何平均を示しています。
注:Wasmtime Pulleyがかなり外れ値であるため、起動ベンチマークには対数スケールを使用する必要がありました。
Wasmi 2.0の実行パフォーマンスへの注力にもかかわらず、その起動パフォーマンスは依然として際立っており、以前のバージョンとほぼ同等です。
結論:ベンチマーク
Wasmi 2.0が最速のポータブルWasmインタープリタのカテゴリに明確に属すると言っても過言ではありません。
次の記事では、wasmi-benchmarksスイートの全結果と発見を発表し、サポートされている多くのWasmランタイムそれぞれにふさわしいスポットライトを当てます。
Wasmi 2.0はなぜそれほど速いのか?
Wasm3とStitchは、Wasmi 2.0と内部で多くの類似点を共有しています。このセクションでは、Wasmi 2.0に採用されたアイデアを詳述すると同時に、関連する箇所でそれらとの類似点と意図的な違いについても議論します。
注:このセクションでは、Wasmとインタープリタの基本的な理解を前提としています。
命令ディスパッチの新しいモード
元のWasmi 1.0のブログ投稿で約束したように、Wasmi 2.0には現在、命令をディスパッチするための4つの異なるモードがあります:
モード
説明
クレートフィーチャー
直接スレッドコード
Wasm3とStitchの両方が使用する最速の設定です。関数ポインタをインタープリタの内部IRに直接埋め込み、テイルコールを使用して命令ハンドラから次のハンドラへジャンプします。
-
間接スレッドコード
直接スレッドコードと非常に似ていますが、IRにオペコードを埋め込み、ディスパッチ時にジャンプテーブルを使用してオペコードを命令ハンドラの関数ポインタにマッピングします。直接スレッドコードより約10〜15%遅いですが、IRのメモリ使用量は大幅に少なくなります。
indirect-dispatch
スイッチループ
Wasmi 1.0で使用されていた手法です。ループとスイッチ(またはmatch)を使用してインタープリタを構築する素朴な方法です。残念ながら、特にApple Siliconではパフォーマンスを大きく損ないます。
portable-dispatch + indirect-dispatch
コールループ
ループ内でテイルコールなしで次の命令ハンドラを呼び出します。残念ながら、非常に遅く、メモリ効率も悪いため、使用はお勧めできません。portable-dispatchとindirect-dispatchが独立したクレートフィーチャーであるため、この組み合わせは設定行列から単純に派生したものです。
portable-dispatch
Wasmiユーザーは以下を使用すべきです:
直接スレッドコード:インタープリタのパフォーマンスを最大化したい場合。
間接スレッドコード:インタープリタのパフォーマンスとメモリ使用量のバランスを取りたい場合。
スイッチループ:テイルコールをサポートしないプラットフォームで実行する場合。
Wasmi 2.0はauto-dispatchクレートフィーチャーを搭載しており、可能な場合は常にスレッドコードベースの設定を自動的に使用します。
命令ディスパッチモードのパフォーマンス
注:CoreMarkの結果は、実行が異なりwasm-coremark-rsプロジェクトを使用したため、上記の直接スレッドコードの結果と完全に一致するわけではありません。
これらの極端なパフォーマンスの違いにもかかわらず、これらの命令ディスパッチモードはすべて、内部で同じインタープリタ実行ロジックとアーキテクチャを共有しています。
Wasmiでの命令ディスパッチ選択がどのように機能するかについて詳細を知りたい場合は、コードをここで見つけることができます:Wasmi Dispatch Selection
実行ハンドラシグネチャ
実行前に、WasmiはWasmバイトコードをWasmi IRに変換します。
各Wasmi IR命令には独自の命令ハンドラ(または実行ハンドラ)があり、命令がどのように実行されるかを定義します。
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引数は基本的に、T型によってプルーニングされたStore<T>です。これは、命令ハンドラがジェネリックになることが許可されていないため重要です。storeは、燃料計測、ホストコール、メモリ.grow、テーブル.grow操作に使用されます。
ip引数は命令ポインタであり、エンコードされた命令ストリームのどこにあるか、どの命令をデコードして実行する必要があるかをエグゼキュータに伝えます。
sp引数は値スタック内での現在実行中の関数の位置です。
mem0およびmem0_len引数は、デフォルトメモリ(メモリ0)への最適化されたアクセスに使用されます。これは、Wasmマルチメモリ提案を使用している場合でも、Wasmでは非常に一般的です。
instance引数は、グローバル、関数、テーブル、メモリ、データ、要素セグメントなどのWasmインスタンス関連オブジェクトをロードするために使用されます。投稿でさらに詳しく説明します。
ireg、freg32、freg64引数は、命令間の途中結果を効率的に格納するために使用される、いわゆる累積レジスタです。投稿でさらに詳しく説明します。
Doneの結果は、実行が停止した理由をエグゼキュータに伝える単なるビットパターンです。より詳細な情報は、後で取得するためにstoreを介して通信されます。
問題:呼び出し規約
Wasmiの命令ハンドラにおける9つの引数のうち7つは、store、ip、sp、mem0、mem0_len、instance、iregの汎用レジスタ(GPR)で値を渡す必要があります。
しかし、sysv64のような一般的な呼び出し規約は、整数引数に対して最大6つのGPRしか提供しません。7番目の整数引数はパフォーマンスを低下させます。