くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

最近ちまたでよく聞くWebAssemblyを触ってみた

f:id:curama-tech:20180907001400p:plain

みんなのマーケットで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を選んでください。

f:id:curama-tech:20180906211000p:plain 選択すると、

エディタ画面が開きます。 f:id:curama-tech:20180907001347p:plain

すでに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ファイルとして直接編集できます。

f:id:curama-tech:20180907001356p:plain

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を紹介してみました。みんなのマーケットに興味がありましたら、ぜひこちらまでご連絡ください。

次回はアプリエンジニアのやんさんです。よろしくおねがいします。