コンテンツへスキップ

言語によるWebAssemblyファイルサイズの違い

WebAssembly自体はプログラミング言語ではありません。対応した各種言語から変換します。そのため、採用する言語によって利用できる機能や開発効率が異なるでしょう。

今回は生成されるバイナリをプログラミング言語ごとに見てみます。

GoとRust

利用したのはGoとRustです。RustはWebAssemblyで最初に採用されているプログラミング言語であり、サイズも最適化されているのではないかと予想されます。対してGoはWebAssemblyを目的としてはいませんし、標準ライブラリなども多数入りそうな予感がします。

どちらもHello Worldを出力するだけのWebAssemblyファイルを作った場合のサイズです。

  • Go : 1,301,957 byte
  • Rust : 1,981,246 byte

となり、意外にもGoのが小さくなります。

Rustのサイズを軽減する

Rustはデフォルトのままでは余計なコードがたくさん入っています。そこで wasm-gc を使ってサイズを軽減するのが基本です。このコマンドは下記コマンドでインストールできます。

そしてコマンドを実行します。

この時生成される hello.min.wasm は207 byteとなっています。大幅に軽減されるのが分かります。

まとめ

Goはまだベータ実装なので、今後のバージョンアップによってサイズが最適化されていくのではないでしょうか。数MBあると、Webアプリケーションとしてはキャッシュの利用を考えたくなるでしょう。

Rustを使うことで小さなWebAssemblyファイルは作りやすくなります(もちろんコード量によりますが)。開発効率性をとるか、サイズをとるかは実行環境によるでしょう。よりシビアに動かす際にはプログラミング言語の選定にも注意が必要です。

インラインでWebAssemblyが使えるinline-wastを試す

WebAssemblyを使う場合、wasmという拡張子のファイルをサーバ上に配置して、それを読み込むのが一般的です。しかし何もファイルが必ず必要な訳ではありません。

そんな試みで作られているのがinline-wastです。WebAssemblyのテキスト版であるWASTを使ってインラインでWebAssemblyを使えるようにするソフトウェアです。

inline-wastの使い方

コード例です。i32.constなどがWASTにあたるコードです。

 

ここで定義されるwastInstructionsの実行結果として返ってくる関数はWebAssemblyなので高速に処理されるものです。興味深いのは文字列で関数を生成しているので、動的にその内容を変更できるということです。処理が長くなりそうな場合、動的にWebAssembly化することで高速化させられる可能性があります。

WebAssemblyはバイナリとテキスト版があります。テキスト版は慣れれば読めないことはない文字列です。面白い仕組みです。

https://github.com/xtuc/inline-wast

TypeScriptからWebAssemblyを生成するAssemblyScriptを試す

WebAssemblyはプログラミング言語ではなく、様々なプログラミング言語から生成されるWebブラウザ上で実行できるバイナリフォーマットになります。基本はRustやC、C++であり、他にもGo、Java、Kotlin、Swiftなど様々なプログラミング言語が対応しています。

しかし複数のプログラミング言語を混ぜて開発するのは要員確保も大変です。そこで注目したいのがTypeScriptからWebAssemblyファイルを生成するAssemblyScriptです。今回はその特徴を紹介します。

JavaScriptの関数を呼び出せる

AssemblyScriptではdeclareを使って関数を定義することで、JavaScript側の関数を呼び出せます。例えば以下はsayHello関数を呼び出しています。

そしてJavaScript側では次のように記述します。mainの中でsayHelloという関数を定義することで、AssemblyScriptからJavaScriptの関数呼び出しを可能にしています。

JavaScriptからAssemblyScriptの関数を呼び出す

逆にJavaScriptからAssemblyScriptの関数を呼び出す場合はexportを使って関数を公開します。型を指定しなければなりません。

これで、WebAssemblyファイル読み込み後の result.instance.exports の中に関数が入ります。

文字列を返す場合

文字列の場合は多少注意が必要です。まず関数の定義の時点で文字列の長さも返すように定義します。

次に受け取った側ではメモリ中の値を読み取り、デコードしなければなりません。

memはWebAssemblyファイルを読み込んだ後 result.instance.exports.memory.buffer で取得できます。

まとめ

WebAssemblyがTypeScriptで書けるようになれば、 TypeScriptだけでフロントエンドの開発が完結できます。もちろんサブセットなので全ての機能が使える訳ではありません。しかし高速な処理が必要になった際に、AssemblyScriptであれば書き慣れた構文でWebAssembly化できるメリットが大きいでしょう。

AssemblyScript/assemblyscript: A TypeScript to WebAssembly compiler 🚀

WebAssemblyファイルをデコンパイルする

WebAssemblyはあらかじめコンパイルされているのでJavaScriptのように実行時にコードをパースする必要がなく、高速に動作します。WebAssemblyのコードはバイナリデータになっているので処理を隠蔽できているように見えますが、デコンパイルを行うことで人間が読める形に変換できます。

今回はwabtを使ってWebAssemblyのデコンパイルを行ってみます。

元になるコード

今回はGoを使っています。一番簡単なコードです。

WebAssemblyにする

このコードをコンパイルしてWebAssemblyにします。

そしてできあがるのが test.wasm です。このファイルは1.3MBありました。

デコンパイルする

ではここからデコンパイルします。その際に使うのがwabtです。WebAssembly用のツールが各種含まれています。ビルド時にはcmakeが必要です。

そして生成されるwasm2watコマンドを使ってデコンパイルします。

デコンパイルすると25.8MBになりました。かなり肥大化しているのが分かります。

コードを見る

デコンパイルされたコードは、WebAssemblyのテキスト版です。

その中に Hello, wasm という文字列も入っています。

つまりWebAssemblyでバイナリ化していたとしても、任意の処理がどこで、どのように行われているか、探そうと思えば探し出せると言うことです。


WebAssemblyでコードをコンパイルしたとしても、100%安全という訳ではありません。Javaと同レベルくらいに考える方が良いかもしれません。隠し方は幾つかありますので、なるべく分かりづらくなる方法を選ぶべきで、安直にキーなどをコードに書かない方が良さそうです。

GoのWebAssemblyでネットワークを利用する

Go1.11からサポートされたWebAssemblyにおいて、ネットワークを利用する方法を紹介します。今回はJSONファイルの取得方法です。

注意点

実際のネットワークアクセスはWebブラウザの開発者ツール、ネットワークタブ内で確認できます。ネットワークアクセスを匿名化したり、CORS制限も適用されますので注意してください。

利用法

今回はCORS制限がなく、APIキーも不要なMicrolinkのコンテンツを取得します。 "net/http" パッケージを使ってネットワークアクセスをします。そして ioutil.ReadAll を使ってコンテンツを取得します。これはバイト文字列になっていますので、 string 関数を使って文字列化します。


これまでのWebAssemblyはDOMやFetch APIなどが使えないのが難点でした。しかしGo版のWebAssemblyによって、その問題はなくなっています。 WebAssemblyの可能性を飛躍的に高めてくれるのではないでしょうか。

GoでWebAssemblyを使ってみよう

Go1.11からWebAssemblyが公式サポートされました。これまでWebAssemblyの開発はRustがメインに使われてきましたが、Goは構文も分かりやすく、非常に良い選択肢になると思われます。

特に面白いのはDOM連携がとても容易な点ではないかと思います。今回はその使い方を紹介します。

まず基本から

Go1.11以降のインストールが終わっている前提とします。例えばコンソールに出力するためには以下のようなコードを書きます。

これをコンパイルします。test.wasmというファイルが作成されます。

Webブラウザで実行する際には、公式に用意されているHTML/JavaScriptファイルを使うと簡単です。

そしてWebサーバを立ち上げて wasm_exec.html を読み込みます。runというボタンがあるので、それを押すと処理が実行されます。

DOM連携

Goの場合、main関数を必ず使うようです。そしてmain関数は引数が使えません。そこで syscall/js パッケージを読み込みます。これを読み込むと、例えば #num1 の値を次のようにして取得できます。

逆にセットする場合は下記のようになります。

Callはメソッドの呼び出し、値の取得はGet、設定はSetと非常にシンプルな形です。例えば足し算の処理は下記のように記述できます。

これで足し算ができます。デモをこちらに置いてありますので体験してみてください。


なお、GoのWASMはサイズが若干大きめです(上記の足し算を行うもので2.5MBあります)。また、関数が使えない(分かっていないだけかも知れません)ので、ファイル数が増えたり、main関数での分岐が必要になるかも知れません。

とは言え、Goの文法でWASMが書けるメリットは非常に大きく、Webアプリケーションの高速化が容易に実現できたり、全体をGoで開発することも夢ではなさそうです。

WebAssemblyがDOM、APIに対応します!

WebAssemblyはWebアプリケーションを高速に処理できる技術ですが、幾つかの欠点もありました。その一つがDOMを処理できないこと、さらにFetch APIやWebAudioなどのAPIが使えない点です。

しかし先日Announcing the web-sys crate! | Rust and WebAssemblyがアナウンスされ、WebAssemblyでもDOMや各種APIの利用が可能となっています。

実際にはラッピング?

今回発表されたのはweb-sysというクレート(パッケージ)です。すでにWebAssemblyに対応しているWebブラウザであれば利用可能となっていることから、WebAssemblyエンジン自体のバージョンアップは不要なようです。そのため、DOMやAPI操作はメインスレッド側のJavaScriptで行っているかと思われます。ただし、Fetch APIで使ったURLなどはWebブラウザ側のソースにはありませんでした(ネットワークログを見ると、どこにアクセスしたかは分かります)。

Hello World

Hello Worldのアラートを出す場合のコードです。

DOM操作

DOMに記述する際のコードです。 web_sys クレートを介してWindowやDocumentにアクセスできます。

Fetch API

Fetch APIを使って外部リソースを取得するコードです。Promiseを使います。


rustwasm/wasm-bindgen: Facilitating high-level interactions between wasm modules and JavaScriptに多数のサンプルが掲載されていますので、ぜひコードを見てください。動いているコードはExamples – The wasm-bindgen Guideにて確認できます。WebGLやCanvas、WebAudioなどのサンプルが掲載されています。