みんなのマーケットでwebエンジニアとして働いている高橋です。
WebAssemblyはブラウザ上で実行できるバイナリフォーマットです。JavaScriptよりも高速に実行できるということは話に聞いていましたが、実際にどのくらい高速になるかは今まで試したことがありませんでした。今回はWebAssemblyに触れてみることから始めて、フィボナッチ数の計算をWebAssemblyとJavaScriptで実行し、それぞれの速度を見ていきます。
環境は
MacBook Pro (Retina, 15-inch, Mid 2014) 2.5 GHz Intel Core i7 16 GB 1600 MHz DDR3
で、Google Chrome 68上にて実行しています。
WebAssemblyをビルドしてみる
WebAssemblyはC/C++やRustからコンパイルすることができます。また、Mozillaが公開しているWebAssembly Studioを用いると、web上でWebAssemblyをビルドすることができます。今回はこれを使ってみます。
アクセスすると Create New Project
というダイアログが開かれるので、Empty C Project
を選んでください。
選択すると、
エディタ画面が開きます。
すでにCのプロジェクトのボイラープレートが作られているので、フィボナッチ数計算用関数を追加し、main関数から呼び出しましょう。
#define WASM_EXPORT __attribute__((visibility("default"))) int fibo(int n) { if (n == 0 || n == 1) { return 1; } return fibo(n - 1) + fibo(n - 2); } WASM_EXPORT int main() { return fibo(40); }
そして、右上のBUild & Run
を押すと、ビルド後main.wasm
ファイルが生成され、
その後wasmが実行されて下のペインに結果が表示されます。(165580141と表示されるはずです。)
このように、WebAssembly Studioを用いると非常に簡単にwasmファイルを作成できます。
wasmファイル、watファイル
バイナリフォーマットのwasmファイルの他、WebAssemblyはデバッグやテストの容易さを担保するためにwasmファイルに変換可能なテキストフォーマットをサポートしています。関数本体はlinear representationという形式で表現されますが、中間形式としてはS式で表現されています(構文はこちらです)。WebAssembly Studioでは、wasmファイルを選択するとS式のテキストフォーマットを.watファイルとして直接編集できます。
C言語のソースにあったmain関数は、.watファイルでいうと
(func $main (export "main") (type $t2) (result i32) i32.const 40 call $fibo)
という箇所にあたります。ここで40
となっているのはfibo関数に与えた引数です。では、これを試しに5
に変更してみましょう。
Run
を押すと、右下のペインに何が表示されているでしょうか? 8
と表示されていれば正しく反映されています。フィボナッチ数は1, 1, 2, 3, 5, 8なので値も正しそうです。
このmain関数をもう少し見ていきます。S式ということで、Lispのコメント;
を借用して注釈をつけると、
(func $main ; 関数定義 (export "main") ; mainをエクスポートする (type $t2) ; 関数の型定義 (result i32) ; 返り値は32-bit integer i32.const 5 ; 5をスタックに積む call $fibo) ; $fibo関数を呼ぶ
という処理になっていることがわかります。 また、.wasmファイルをダウンロードしてダンプしてみると、
00000: 41 05 00 00 00 | i32.const 5 000006: 10 01 00 00 00 | call 1 00000c: 0b | end
となり、S式と対応していることがわかります。
JavaScriptとの速度比較
次に、JavaScriptで記述したフィボナッチ関数と、wasmとの速度を比較してみましょう。
JavaScriptのコードは以下となります。
"use strict"; function run() { function fibo(n) { if (n == 0 || n == 1) { return 1; } return fibo(n - 1) + fibo(n - 2); } console.log(fibo(40)); }
それぞれブラウザ上で実行してみると、
JavaScript | WebAssembly | |
---|---|---|
fibo(40) | 1781ms | 523ms |
ということで、WebAssemblyのほうが1781/523 = 約3.4倍速いという計測結果になりました。 CPUの負荷が大きい処理については今後はWebAssemblyを利用することによって高速なweb体験をユーザーに提供できるようになるかもしれません。
(おまけ)WebAssemblyをgolangから作成する
もしgo1.11の環境があれば、golangからもwasmを生成することができます。こちらの記事(https://qiita.com/cia_rana/items/bbb4112b480636ab9d87)を参考にgolangからwasmを生成してみました。
コードは、
package main import "fmt" func fibo(n int) int { if n == 0 || n == 1 { return 1 } return fibo(n-1) + fibo(n-2) } func main() { fmt.Println(fibo(40)) }
そして、以下のコマンドでビルドすることができます。
GOOS=js GOARCH=wasm go build -o ./test.wasm wasm.go
こちらを実行した結果は以下になります。
JavaScript | WebAssembly | WebAssembly(golang) | |
---|---|---|---|
fibo(40) | 1781ms | 523ms | 5072ms |
なぜかgolangから生成したwasmはJavaScriptよりも実行時間が長いですね。フィボナッチ数の計算だけではなく、 fizzbuzzを大量に実行したりしましたが、golangから生成したwasmはJavaScriptよりも遅いパターンしか計測できませんでした。 (この件に関しては深く追っていませんが、詳しい方は教えていただけると嬉しいです。)
おわりに
今回の記事ではWebAssemblyを紹介してみました。みんなのマーケットに興味がありましたら、ぜひこちらまでご連絡ください。
次回はアプリエンジニアのやんさんです。よろしくおねがいします。