Webアプリはネイティブアプリと比べてロードが遅い、通知が出せない、実行が遅いといった課題を持つ。だがProgressive Web AppとWebAssemblyを活用し、ネイティブアプリと遜色のない速さを実現させたサイトも登場している。
html5j Webプラットフォーム部では今回「Progressive Web AppとWebAssembly」をテーマに勉強会を開催した。
WebAssembly(WASM)でできること
WebAssembly(WASM)コミュニティを率いている清水智公さんが語ったのは、WASMでどういうことができるのか、どうしたら作れるのか、使う際に気をつけることについて。セッションタイトルは「できる!WebAssembly」。
Unityで作ったゲームが任天堂SwitchにもWebにも展開できるものが登場している。これはUnityがすごいという話ではなく、これを実現している技術がWebGL、WASMという技術であるということ。
WASMは、ゲームのような重たいプログラムをどこでも速く動かすために使われる。例えばUnityで作ったコンテンツ、Unityちゃんが踊ったりするシーンで、エフェクトの処理などに用いられたりするが、それだけではない。
一つは他の言語の資産をWebに展開する場合。もう一つはCPUインテンショナルな処理を高速化する場合である。処理が重くなる理由は2つある。計算量が大きいこと、もしくは読み込むデータ量が多い場合だ。
どちらかに効くかというと、CPUが速ければ速くなるという処理にWASMは効いてくる。2016年3月に行った調査によると、ネイティブで書いたときに比べ、1.5倍ぐらいのスピードで動くようになる。
WASMはモダンなブラウザすべてで使えるようになっている。忘れてはいけないのは、Node8でも動くこと。サーバサイドの処理を速くすることもできる。WASMはバイナリーコード。だがファイルなので、手で書くこともできるが、たいていの人はツールを使う。WASMを生成ツールには次のようなものがある。
例えばCなら、EmscripitenかClangを使えばコンパイルできる。TypeScriptならAssembryScriptを使うことを覚えておけばよい。
WASMでできることは数値計算、線形メモリへのアクセス、関数の呼び出しである。
使い方は次の通り。
●JavaScript embedding API
WASMファイルを用意する。WASMモジュールをコンパイルする。WASMモジュールをインスタンス化する流れとなっている。インスタンス化がなぜ必要かというと、WASMはWASMからエクスポートした関数を使うだけでなく、WASMに関数をインポートして与えることができる。
どんな関数やメモリを与えるかはその場でコントロールする。このWASMにJavaScriptの関数を与える機能は非常に強力となっている。またWASMはnodeでも使える。
●Web embedding API
Webで使うときはもっと簡単に使うために、Web embedding APIとして定義されている。Webページ上での実行を前提にしているAPIでストリームコンパイルを行う。
●WebAssembly.Memory
WASMは数値演算しかできない。それを使って複雑なことを扱うために、メモリを擬似的に持つ。それがWebAssembly.Memoryである。WASMの作業用メモリ(ヒープとデータ)で、初期サイズと最大サイズをコンストラクタで指定する。サイズはページ64KB。実データはArrayBuffer上に取られる。リトルエンディアンとなっている。
- メモリの使用例:Cの文字列の場合
- メモリの利用例:Cの構造体の場合
上述の通り、WASMは様々なツールを使って出力できる。その一つがAssemblyScript。これはTypeScriptのサブセットを処理して、WASMを出力するツールである。
その一つがAssembryScriptで、これはTipeScriptを変化させたもの。TipeScriptとの違いは、そのまま変換できないこと。数値をより細かく片付けする必要がある。WASMで定義されている演算子が新しい演算子として追加される、NaN/Infinityは型が変わっていることなどが挙げられる。
例えば、足し算するコードはこんなコードに変わる。
オブジェクト指向のコードは変えられるが、AssembryScriptの場合、コンストラクタはメモリを初期化する関数に、メソッドはポインタを受け取る関数になる。
WASMを使うときに注意すべき点とは
WASMを使うときに最初に気をつけないといけないのは、うっかりするとサイズが大きくなってしまうということ。CでHello Worldを変換すると、C言語の標準ライブラリーも合わせて変換されて付け加えられるため、240KBくらいになってしまう。
Unityちゃんが踊っているものなどは、アセットが46MB、コードも3.5MBぐらいある。そこでキャッシュを上手く使うことが求められる。
タイミングは2つある。WASMのファイルはfetchで取ってくるが、そのタイミングでキャッシュする。
もう一つは、コンパイルをした後に、WASMのモジュールというオブジェクトができる。これは structuredCopyが実装されているので、IndexedDBにコピーできる。ここでキャシュする。
次にメモリをとても意識するようになること。WASMで使っているメモリとJavaScriptのメモリは別。JavaScriptで使っているメモリの一部にWASMのメモリが乗っている。
例えばUnityの場合、ブラウザに割り当てられたメモリの一部にJavaScriptが割り当てられていて、その一部にUnity用のメモリがあり、その一部を使う。
Unityのメモリ領域はブラウザの開発ツールからは見えないので、Unityのプロファイラーで見ることになるため、メモリマネジメントは難しい。
第三にツールチェーンが複雑になること。これは徐々に良くなってきている。Rustだとrust-webplatformというライブラリがある。
またWebpackの開発計画に、WASMへの対応がある。これが実装されると、WASMをJavaScriptのプロジェクトへの組み込みが容易になる。JavaScriptのモジュールをロードできるようになるという。実装はこれから。
第四はデバッグがつらいこと。いまのところ、WASMに埋め込まれたデバッグ情報を上手に利用できていない。そうはいっても、デバッグは必要なので、開発が行われている。
一例として、Firefoxの開発ツールはWASMのソースマップ表示に対応する機能が追加されようとしている。
WASMは進化しており、メモリに関してもGCのサポートが入っている。これはオプショナルなので、使うかどうかはプログラマが選択できる。他言語で速く書きたい場合は、GCをオンにする使い方もできる。
WASMはWebページすべてで使うのではなく、プロファイリングして効きそうなところを選択するのが正しい使い方。WASMの良いところは、Cで作った資産を持ってこられること。ツールも進化しているので、Cの資産を持っている人はWASMを使って、Webに展開してみよう。
関連ページ
イベントの様子は動画でも公開中
※本記事は「CodeIQ MAGAZINE」掲載の記事を転載しております。