本セッションの登壇者
セッション動画(YouTubeチャンネル登録もお願いします。)
皆さん、こんにちは。
今日はJavaScriptによる動的WebAssmebly生成というお話をしたいと思います。あまり時間がないので駆け足でいきたいと思います、よろしくお願いします。
今日のお題は、JavaScriptやWebAssemblyのコードを動的に生成して、そのプロファイルを取って比較してみるということをやってみたいというふうに思っております。
目的はあくまでもプロファイルの比較であって、今回Brainfuck(以下BF)を利用するんですが、BFの高速化ではないということにご注意ください。

BFとは
まずそのBFとは何ぞやという話なんですけれども、いわゆる「難解言語にカテゴライズされる、8種類の記号」と書きましたが、今日は入力を使わない7種類になるんですが、7種類だけの記号で構成されたチューリング完全な言語となっておりまして、たとえばHello Worldを出力するコードみたいなものが こんな感じ なんですが、見てもわからないという形になります。

今日実行するコードは、いわゆるマンデルブロー集合と呼ばれるフラクタル図形を描画するコードで、大変重いのでこういうベンチマークがあると非常に便利なので、今回使おうと思います。

実際のコードは こちら になっていて、だいたい11キロバイト、よく分かんないですね、というものを実際に実行して、6つのプログラムでプロファイルを取ってみたいと思います。

コードの紹介
その6つのプログラムなんですけども、まずJavaScriptで単純に実行する、もしくはWebAssemblyで単純に実行する、これが1番と4番になります。
それと違って今回の特色としては、2番、3番、5番、6番なんですけれども、動的にJavaScriptのコードを生成する、もしくは動的にWebAssemblyのコードを生成するということをやってみたいと思います。この単一関数/複数関数というのは後でご紹介します。

まず簡単な実装、JavaScriptで単純に実行するんですけれども、これはひとつひとつ記号が出てくるたびに、switch文でその記号を見て実際のコードを実行すると。

なのでwhileでポインタ、いわゆるプログラムカウンタというやつをどんどん右に行ったりジャンプしたりさせながら、ひとつひとつ文字を見て、それに対応するものを実行するという形で、一番簡単でシンプルな実装になります。
これをJavaScriptもしくはWebAssmelbyで実装しました。

コードの構成がほとんど変わらないように作りましたので、プロファイルを比較するときに、あまりプログラムの実装の差が出ないように気を使っております。
今回のポイントなんですが、動的にJavaScriptのコードを生成するとはどういうことかというと、たとえば+みたいなものがあった場合に、その実際の+の中身を文字列でこのようにfuncBodyにぼんぼん足していったりするんです。
+が来るとこんな感じでmemory[pointer]を足して、括弧が来るとwhile文ができて、それを文字列としてバッと作って最終的にnew Functionで1つの関数にする、そのように動的に生成するというのをJavaScriptでやりました。
WebAssemlbyでも同じようなことをやってですね、基本は同じなんですけれどもWebAssmeblyはバイナリなので、ハンドアセンブルしてバイナリをボコボコボコボコ入れていってですね、最終的にWebAssembly.instantiateを使ってコンパイルして、それを実行するということをやってみました。

ただBrainfuckのコードを1対1で変換していくので、そうすると1つの関数がむちゃくちゃでかくなって、最適化を阻害する可能性があるんですね。なので複数関数に分割するというコードも書いてみました。

具体的には括弧が出てくるたびに、そこの内容を再帰で外の関数に出して、その関数を呼び出すということをやって、1つの関数がでかくなり過ぎないようにしています。
今回のマンデルブロー集合のプログラムは、686個の括弧がありますので全部で687個の関数を作り出すというようなことをやってみました。
プロファイル結果
実際に動かしたプロファイルの結果をお見せしたいと思うんですけれども、まずPC Chromeですね。

JavaScriptで単純に実行したものとWebAssebmlyで動的に作成したものとで、だいたい50倍ぐらいの差が出ているわけです。非常に大きいですね。
単純にWebAssemblyにするだけでも3倍ぐらい速くなっていたりとか、もしくは動的に作成してもすごく速くなっていて、さらに複数関数に分けると4倍ぐらい速くなっていたり、わかりやすく最適化が効いてくれる例になっているな、と思いました。
なんにせよ、WebAssemblyをうまくかけるとすごく速くなることは見ていただけるかと思います。

PC Safariをお見せしたいんですけれども、WebAssemblyを動的に生成すると、なんとすごく遅くなるんですね。単純に実行するよりもさらに倍くらい遅くなるようなことが起こってしまっています。

これはなぜかというと、最適化が一切かかっていないことがおそらく読み取れるんじゃないかな、と思っていまして。最適化がかかるorかからないがすごく大事だということがわかってくるんですけれども、それも複数関数にすることでむちゃくちゃ速くなって、最終的にやはり50倍くらい速い実行結果が得られます。最適化がどのようにかかるかというのはブラウザによっても大きく変わりますし、なかなか簡単に読みきれないところもあって、この傾向はMoblie Safariも同じなんですけれど、そういったところを考えながら最適化とか考えていく必要はあるのですが、うまくかかるとすごく速い!というのが今回のまとめになります。

単純にWebAssemblyにするだけでも相当速いですが、うまく最適化できると50倍ぐらい速くなることが言えてですね、言語のランタイムをWebAssemblyとかで作ろうとする方にとってはけっこう役に立つテクニックになると思うので、心のどこかに留めていただけると、きっと将来使うことがあるかもしれない…と思っております。

今回の話はいろいろと知見にまみれていたので、ちょっと丁寧にブログにまとめましたので、よろしければそちらをご覧いただければと思います。

ご清聴ありがとうございました。引き続きTechFeed Conferenceをお楽しみください。失礼いたします。