11月5日、マサチューセッツ大学アマースト校のAbhinav Jangda氏、Bobby Powers氏、Emery D. Berger氏、Arjun Guha氏が「Not So Fast: Analyzing the Performance of WebAssembly vs. Native Code」と題した論文を公開した。この記事では、WebAssembly(Wasm)の実行性能がネイティブコードとどの程度差があるのかを、実際の大規模アプリケーションを用いて体系的に分析した研究について詳しく紹介されている。以下に、その内容を紹介する。
WebAssemblyとネイティブコードの「性能差」を検証する
WebAssembly(以下Wasm)は、C/C++などの言語をブラウザ上で安全かつ高速に実行するための低レベルバイトコードである。これまでの研究では「ネイティブコードに近い性能」を持つとされてきたが、その多くはわずか100行程度の科学技術計算向けカーネル(PolybenchC)を対象にしていた。
著者らはこれを「WebAssemblyの本来のユースケースを代表していない」と指摘し、より現実的な負荷を持つSPEC CPU2006および2017ベンチマークを利用して、包括的な性能評価を実施した。
しかし、Unixアプリケーションをブラウザ上でそのまま動かすには、ファイルシステムや同期I/Oなどの標準APIが存在しないという課題がある。これを解決するため、研究チームは「Browsix-Wasm」と呼ばれる新しいシステムを開発した。
Browsix-Wasmの仕組みと改良点
従来のBrowsixはJavaScript上にUnixカーネルを模倣していたが、Wasmを直接サポートしていなかった。そこで著者らは次の改良を施した:
- WebAssembly対応:Emscriptenを拡張し、ネイティブのUnixプログラムをWasmへ変換して実行可能にした。
- 性能改善:ファイル書き込みやパイプ通信などのシステムコール処理を最適化し、メモリコピーのオーバーヘッドを最小限に抑えた。
- 並列実行サポート:64MBの共有バッファを設け、WebWorker間通信の性能を改善した。
これにより、LaTeXのような複雑なアプリケーションでもソースコードを改変せずブラウザ内で動作可能になった。

図:SPECベンチマークをブラウザ内で実行するためのフレームワーク構成
実験:Wasmは平均で45〜55%遅い
研究チームは「Browsix-SPEC」と呼ばれる自動実験ハーネスを開発し、ChromeとFirefoxでのWasm実行性能をネイティブ版と比較した。結果は次の通りである。
| 指標 | Chrome | Firefox |
|---|---|---|
| 平均遅延率 | 1.55倍 | 1.45倍 |
| 最大遅延 | 2.5倍 | 2.08倍 |
| 例外的に高速なケース | 429.mcf, 433.milc |
一方で、PolyBenchCのような小規模カーネルでは、オーバーヘッドは平均0.2%とほぼ無視できるレベルであった。このことから、実アプリケーション規模になるほど性能差が顕著に現れることが分かる。
性能低下の主な要因
論文では、ハードウェアのパフォーマンスカウンタを用いて詳細な原因分析を行っている。その結果、次のような要因が判明した。
レジスタの利用効率が低い
- Chromeではロード命令が2.02倍、ストア命令が2.30倍多い。
- 一部のレジスタ(例:r10, r13, r15)はJavaScriptエンジンによって予約されており、Wasmからは利用できない。
ブランチ命令の増加
- 安全性を保証するためのスタックオーバーフロー検査や関数テーブル検査により、条件分岐が増加。
- Chromeでは条件付きブランチが1.65倍に増加。
コードサイズの肥大化とキャッシュミス
- 命令数がChromeで1.8倍、Firefoxで1.75倍。
- L1命令キャッシュミスはChromeで2.83倍、Firefoxで2.04倍。
- これによりCPUサイクルも1.5倍程度増加した。
| パフォーマンスカウンタ | Chrome | Firefox |
|---|---|---|
| all-loads-retired | 2.02 | 1.92 |
| all-stores-retired | 2.30 | 2.16 |
| branch-instructions | 1.75 | 1.65 |
| instructions-retired | 1.80 | 1.75 |
| L1-icache-load-misses | 2.83 | 2.04 |
ケーススタディ:行列積の比較
論文では具体例として、単純な行列積関数 matmul のCコードを解析している。
void matmul(int C[NI][NJ], int A[NI][NK], int B[NK][NJ]) {
for (int i = 0; i < NI; i++)
for (int k = 0; k < NK; k++)
for (int j = 0; j < NJ; j++)
C[i][j] += A[i][k] * B[k][j];
}
この関数をClangとWasmでそれぞれコンパイルすると、Chrome上のWasm実行は2〜3.4倍遅い。理由は次の通りである。
- Clangが28命令で生成するコードを、ChromeのJITは53命令に膨らませている。
- Chromeはx86のメモリアドレッシングモードを十分に活用できておらず、一つの加算操作に3命令を要する。
- 不要なジャンプ命令やレジスタスピル(メモリ退避)が発生している。
この例は、WebAssemblyのJITコンパイラがネイティブコンパイラに比べてコード生成が未成熟であることを示している。

図:行列サイズごとのWasm性能。ネイティブの2〜3.4倍の実行時間を要する。
改善の余地と今後の展望
著者らは、発見した性能問題を次の2種類に分けて考察している。
実装改善で解決可能な問題
- レジスタ割り当てアルゴリズム(線形スキャン)を改善。
- 命令選択・ループ最適化の強化。
- ホットパスの再コンパイルなど、JIT特有の最適化を導入。
設計上避けられない制約
- 安全性のための動的チェック(スタックや関数型整合性の検証)。
- JavaScriptエンジンとの共存によるレジスタ予約。
これらの一部は、将来的なWebAssemblyの設計変更(例:型安全な関数ポインタやメモリモデルの拡張)によって解消できる可能性があると指摘している。
まとめ
本研究は、WebAssemblyの性能を体系的に分析した初の大規模実験であり、以下の重要な結論を導き出している。
- 現行ブラウザでのWasm実行は、平均でネイティブより45〜55%遅い。
- 主な要因は、レジスタ圧迫・コード肥大・安全性チェックに起因する。
- これらは実装改善やアーキテクチャ進化によって今後縮小できる可能性がある。
Wasmは依然としてブラウザにおける高速実行環境として有力であり、今後の最適化によって「ネイティブ並みの性能」という当初の目標に近づくことが期待される。
詳細はNot So Fast: Analyzing the Performance of WebAssembly vs. Native Codeを参照していただきたい。