LLVM の WebAssembly バックエンドと Emscripten (翻訳)

WebAssembly は通常ソース言語からコンパイルされて作られるので、開発者が WebAssembly を利用するときにはコンパイルのためのツールが必要になります。このため V8 チームは関連するオープンソースプロジェクト (LLVM, Emscripten, Binaryen, WABT) に取り組んできました。この記事では Emscripten と LLVM に関して私たちが行ってきたことを説明します。これによって、Emscripten がデフォルトで使うバックエンドは LLVM の WebAssembly バックエンドに変更されます ぜひ試して、問題があれば教えてください!

私たちはオープンソースの WebAssembly ツールコミュニティと協力しながら、LLVM の WebAssembly バックエンドおよびその Emscripten への組み込みを行ってきました。そのため Emscripten では少し前から WebAssembly バックエンドを利用可能になっています。そして現在では LLVM を使ったバックエンドがほとんどの性能指標において古い "fastcomp" バックエンドを上回るようになったので、LLVM の WebAssembly バックエンドをデフォルトに切り替えようというわけです。ただしまずできる限りのテストを行う必要があるので、この記事が書かれた時点では切り替えられていません。

このアップグレードが重要なのは、次のエキサイティングな理由によります:

試す

WebAssembly バックエンドを試すには、最新版の emsdk で次のコマンドを実行してください。

emsdk install latest-upstream
emsdk activate latest-upstream

ここで "upstream" は LLVM の WebAssembly バックエンドが (fastcomp とは異なり) LLVM のアップストリームであることを示します。またアップストリームが使われていることから、プレーンの LLVM と clang を自分でビルドする場合には emsdk を使う必要がありません! (Emscripten でそのビルドを使うには、.emscripten ファイルにパスを追加するだけで済みます)

現在では emsdk [install|activate] latest を使うと fastcomp が使われます。また"latest-fastcomp"というオプションを指定しても同じ動作となります。将来バックエンドが切り替わると、"latest"が現在の"latest-upstream"と同じことになり、fastcomp は"latest-fastcomp"を通してのみ利用可能になります。fastcomp は利用価値のある間はオプションとして残ります。詳細については記事の最後を見てください。

歴史

LLVM の WebAssembly バックエンドは Emscripten にとって三つ目のバックエンドであり、外部のライブラリとしては二つ目です。最初のバックエンドは JavaScript で書かれており、テキスト形式の LLVM IR をパースしていました。これは 2010 年当時に行われた実験として価値のあるものでしたが、LLVM のテキスト形式が後から変更される可能性があったり、コンパイルが遅いなど、明らかな欠点もありました。2013 年には新しいバックエンドが LLVM のフォークとして作成され、"fastcomp" と名付けられました。fastcomp は asm.js を出力するように設計されていますが、最初の JS バックエンドがこれを行うにはハックが必要でした (上手く行ったわけでもありません)。asm.js を使った結果として、生成されるコードの質とコンパイル時間が大幅に改善されました。

以上は Emscripten に関する比較的小さな変更でした。たしかに Emscripten はコンパイラですが、最初のバックエンドと fastcomp はプロジェクトの中でもほんの一部にしか過ぎません。はるかに多くのコードは他の部分、例えばシステムライブラリ、ツールチェインの統合、言語のバインディングなどに費やされています。そのためコンパイラバックエンドの変更というのは根本的な変更と言えますが、影響を受けるのはプロジェクト全体の一部分だけです。

ベンチマーク

コードサイズ

コードサイズの計測 (低い方が良い、計測値は fastcomp で正規化してある)。
コードサイズの計測 (低い方が良い、計測値は fastcomp で正規化してある)。

グラフからは、LLVM の WebAssembly バックエンドを使うとほとんどの場合においてサイズが小さくなることが分かります! サイズの差は小規模なマイクロベンチマーク (左側、名前が小文字のもの) で大きくなっていますが、これはシステムライブラリの改善が他の部分よりも大きいためです。ただし右側の実際に使われているコードベースに対するマクロベンチマーク (名前が大文字のもの) においても改善は見られています。唯一マクロベンチマークの LZMA においては結果が悪化していますが、これは新しい LLVM が異なるインライン戦略を使った結果、不幸にもコードサイズが増えてしまったためです。

平均すると、マクロベンチマークのコードサイズは 3.7% 小さくなりました。コンパイラのアップグレードとして悪くないでしょう! テストスイートに入っていない実際に使われているコードベースにおいても同様の改善が見られています。例えばゲームエンジン Cube 2 を Web に移植した BananaBread ではコードサイズが 6% 縮小し、Doom 315% 縮小しました!

コードサイズの改善 (および次で説明する速度の改善) は次の理由によります:

速度

速度の測定結果 (低い方が良い、計測は v8 で行った)。
速度の測定結果 (低い方が良い、計測は v8 で行った)。

マイクロベンチマークでは速度の改善が見られたり見られなかったりしますが、これは驚くようなことではありません。というのも多くのマイクロベンチマークのパフォーマンスは一つの関数、ときには一つのループによって支配されるので、Emscripten が生成するコードがほんの少し変わっただけで、VM による最適化が良くなったり悪くなったりするためです。マイクロベンチマークでは速度が改善したものと悪化したものがほぼ半々となっていますが、現実的なマクロベンチマークを見ると、不幸なインライン化により速度が低下した LZMA を除けば、全てで改善が見られます!

平均すると、マクロベンチマークにおける速度の改善は 3.2% でした。

ビルド時間

BananaBread を使ったコンパイル時間とリンク時間の測定結果 (低い方が良い、fastcomp で正規化してある)。
BananaBread を使ったコンパイル時間とリンク時間の測定結果 (低い方が良い、fastcomp で正規化してある)。

ビルド時間に対する影響はプロジェクトによって異なりますが、ここでは BananaBread の例を示します。BananaBread は完全な軽量ゲームエンジンであり、95,287 行のコードを含む 112 個のファイルからなります。グラフの左側に示されているのはコンパイルステップ、つまりソースファイルをオブジェクトファイルにコンパイルする処理にかかる時間であり、両方のコンパイラにおいてデフォルトの -O3 オプションを使っています。グラフからは、WebAssembly バックエンドにおいてコンパイルステップにかかる時間が fastcomp よりも少しだけ長いことが分かります。WebAssembly バックエンドが行う処理は fastcomp より多いので、これは不思議なことではありません fastcomp はソースコードをビットコードにするだけですが、WebAssembly バックエンドではビットコードをさらに WebAssembly にコンパイルします。

グラフの右側で示されているのはリンクステップ、つまり最終的な実行可能を生成するのにかかる時間です。ここではインクリメンタルビルドにおいて使われる -O0 オプションを使いました (完全な最適化を行うビルドなら、リンク時にも -O3 を使った方がいいでしょう。下記参照)。グラフからは、コンパイルステップでわずかに余計にかかった時間が割に合うことが分かります: リンクは 7 倍高速化されているからです!

繰り返しになりますが、ビルド時間に対する影響はプロジェクトによって異なるはずです。BananaBread よりも小さなプロジェクトでは高速化は小さく、大きなプロジェクトでは高速化も大きくなるでしょう。また最適化も影響します。上述の通りこのテストは -O0 を使っていますが、リリースビルドにおいては -O3 が必要になるでしょう。この場合 Emscripten は最終的な WebAssembly に対して Binaryen オプティマイザを起動し、meta-dce を実行し、そのほかにも様々な最適化を行ってコードサイズと速度を向上させます。もちろんこれには追加の時間がかかりますが、リリースビルドにおいてはこれをする価値があります 例えば BananaBread では WebAssembly が 2.65 MB から 1.84 MB になり、30 % 以上縮小されます。しかしインクリメンタルビルドにおいては、 -O0 を使ってこういった最適化を飛ばすことができます。

既知の問題

一般的に言って LLVM の WebAssembly バックエンドはコードサイズと速度の両方で優れていますが、私たちはいくつか例外を確認しています:

その他の変更

Emscripten の機能の一部は fastcomp や asm.js と結びついており、そういった機能は WebAssembly バックエンドを LLVM に切り替えると利用できなくなってしまいます。私たちはそういった機能の代替の開発にも取り組んできました。

JavaScript の出力

WebAssembly でないコードを出力する機能が重要になるケースもあります 主要なブラウザは全て WebAssembly をサポートしますが、それでも WebAssembly をサポートしない古いマシンや古い携帯電話は残り続けるからです。また WebAssembly に新しい機能が追加されたときにもこの機能は意味を持ちます。JS へコンパイルすると実行速度やサイズが悪化しますが、誰でもコードを実行できることが保証されるからです。fastcomp では asm.js の出力がそのまま JavaScript として使うことができましたが、LLVM の WebAssembly バックエンドを使う場合には何か他のものが必要になります。私たちは Binaryen の wesm2js を使っています。これは名前の通り、WebAssembly を JS にコンパイルします。

詳しく説明するにはブログ記事がもう一つ必要になりますが、ここで鍵となる設計上の判断を簡単に言うと、asm.js をサポートする必要がもはや無いのです。たしかに asm.js は通常の JS よりもはるかに高速に動作しますが、asm.js の AOT 最適化をサポートするブラウザの事実上全てが、何らかの形で WebAssembly もサポートしているということが判明しました (例えば Chrome は asm.js を内部で WebAssembly に変換することで最適化を行っています!)。そのため JS のフォールバックについて考えるときには、asm.js も使えないと考えた方が現実的なのです。そればかりか、その方が物事が単純になります: 例えば新しい WebAssembly の機能をサポートするときに必要となる JS コードは格段に小さくなります! 以上の理由により、wasm2js は asm.js をターゲットとしません。

しかしこの設計による副作用として、fastcomp を使った asm.js ビルドと LLVM の WebAssembly バックエンドを使った JS ビルドを比較すると、asm.js の方がずっと速くなります asm.js の AOT 最適化を行うモダンなブラウザでテストすればの話ですが。おそらくあなたのブラウザでもそうなるでしょう。しかし WebAssembly が使えないブラウザではこうなりません! 二つを正しく比較するには、asm.js を最適化しないブラウザ、あるいは機能が無効化されたブラウザを使う必要があります。もしそれでも wasm2js の方が遅かったら、ぜひ教えてください!

wasm2js は動的リンクや pthread といったあまり使われていない機能が実装されていませんが、多くのコードは既に動作するはずです。また fuzz テストも注意深く行われています。JS 出力を試すには、 -s WASM=0 として WebAssembly を無効化してください。こうすると emccwasm2js を実行し、リリースビルドであればそれから様々な最適化が行われます。

その他

最後に

現在の私たちの目標はこの変更に関連するあらゆるバグを修正することです。ぜひ試して、問題を報告してください!

プログラムが安定すれば、デフォルトのコンパイラバックエンドはアップストリームの WebAssembly バックエンドに切り替えられます。なお前述の通り、fastcomp はオプションとして残り続けます。

私たちはいずれ fastcomp を完全に削除するつもりです。そうすれば保守に必要な大変な作業が必要なくなり、LLVM の WebAssembly バックエンドへの新機能の追加や Emscripten 全体に関する改善などに集中できます。fastcomp の削除予定表の計画を始められるよう、あなたのコードベースでテストが上手く行くかどうかぜひ教えてください。

謝辞

LLVM の WebAssembly バックエンド、wasm-ld、Binaryen、Emscripten、そしてこの記事で触れた他のプログラムに関わった全ての人に感謝します! そんな素晴らしい人々の一部をここに挙げます: aardappel, aheejin, alexcrichton, dschuff, jfbastien, jgravelle, nwilson, sbc100, sunfish, tlively, yurydelendik。

Attribution

Portions of this page are reproduced from work created and shared by the V8 project and used according to terms described in the Creative Commons 3.0 Attribution License.

The original source page is Emscripten and the LLVM WebAssembly backend V8.

広告