WebAssemblyをnginxサーバーで動かす。jsとwasmで実行時間比較


はじめに

WebAssemblyが注目されているので使ってみました。環境はUbuntu 18.04です。- Interacting with code — Emscripten 1.39.8 documentationを主な参考にしました。

Emscriptenのインストール

sudo apt install emscriptenでいけるかなと思ったのですが、emccでコンパイルしようとすると

emcc sqrt.cpp -o function.html -s EXPORTED_FUNCTIONS='["_int_sqrt"]' -s EXTRA_EX
PORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
WARNING  root: LLVM version appears incorrect (seeing "(/b/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project", expected "3.4")
INFO     root: (Emscripten: Running sanity checks)
CRITICAL root: Cannot find /home/endo/Documents/workdir/emsdk/upstream/bin/lli, check the paths in ~/.emscripten

とよくわからないエラーが出たので公式に従って

# Get the emsdk repo
git clone https://github.com/emscripten-core/emsdk.git

# Enter that directory
cd emsdk
# Fetch the latest version of the emsdk (not needed the first time you clone)
git pull

# Download and install the latest SDK tools.
./emsdk install latest

# Make the "latest" SDK "active" for the current user. (writes ~/.emscripten file)
./emsdk activate latest

# Activate PATH and other environment variables in the current terminal
source ./emsdk_env.sh

とやりました。シェルにfishを使ってたのが問題かもしれませんが未検証です。

C++のファイル

loop.cpp
extern "C" {

int loop(int num) {
  for (int n0 = 0; n0 < num; n0++) {
    for (int n1 = 0; n1 < num; n1++) {
      for (int n2 = 0; n2 < num; n2++) {
        if (n0 + n1 + n2 == (num - 1) * 3) {
          return n0;
        }
      }
    }
  }
  return 0;
}
}

とりあえず重い処理をさせたかったので計算量$O(n^3)$の処理を書きました。

emcc loop.cpp -o loop.html -s EXPORTED_FUNCTIONS='["_loop"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'

でコンパイルします。

loop.htmlloop.jsloop.wasmが今回生成としてファイルです。

html (JavaScript)

loop.htmlは無視してコードを書きます。WebAssemblyを利用した場合とJavaScriptで同じ処理を書いた場合で実行時間を比較できるようにしてみました。

loop.html
<button id="loopc">loop with C</button>
<button id="loopj">loop with js</button>

<script>
  // WebAssemblyを使う。
  let loopcBtn = document.getElementById("loopc");
  let loopc;
  Module = {
    onRuntimeInitialized: function() {
      loopc = Module.cwrap("loop", "number", [
        "number",
      ]);
    },
  };
  loopcBtn.addEventListener("click", function() {
    const startTime = performance.now();
    console.log(loopc(2000));
    const endTime = performance.now();
    console.log(endTime - startTime);
  });

  // javascriptで同じ処理をしてみる (比較用)
  let loopjBtn = document.getElementById("loopj");
  function loopj(num) {
    for (let n0 = 0; n0 < num; n0++) {
      for (let n1 = 0; n1 < num; n1++) {
        for (let n2 = 0; n2 < num; n2++) {
          if (n0 + n1 + n2 === (num - 1) * 3) {
            return n0;
          }
        }
      }
    }
    return 0;
  }


  loopjBtn.addEventListener("click", function() {
    const startTime = performance.now();
    console.log(loopj(2000));
    const endTime = performance.now();
    console.log(endTime - startTime);
  });
</script>

<script src="./loop.js"></script>

nginxの設定

Incorrect response MIME type. Expected 'application/wasm' エラーの 対応 - Qiitaの記事を参考にさせていただきました。nginx連載3回目: nginxの設定、その1 - インフラエンジニアway - Powered by HEARTBEATSを参考にしながら、/etc/nginx/mime.types

application/wasm                                wasm  

を書き足してnginxを再起動します。

実行結果

実行時間
wasm 17721.55 [ms]
js 10688.32 [ms]

wasmのほうが今回は時間がかかりました。

WebAssemblyとは?〜実際にC言語をブラウザで動かす〜【2019年6月版】 - Qiitaに書いてあるように実行時間が短くなるわけではなくファイルサイズが小さくなってロード時間が短くなるのが恩恵なので妥当な結果ではありますが、ちょっと残念ではあります。なお、実行したブラウザはGoogle Chromeのversion 80.0.3987.122です。

参考にさせていただいたリンク集