Improve Web Application Performance using WASM

WASMって何?

  • ブラウザーで実行できるバイナリーフォーマット
  • シンプルなstack machineとして考えられる(JavaやPythonのバイトコードと同様)
  • 型が少ない
  • バイト指定可能なメモリー
  • http://webassembly.org/docs/semantics/

WASMは速い!

JavaScript

WASM

いろんな言語をWeb上で使えるようになります

  • C/C++
  • Rust
  • Go(まだまだ)
  • などなど

WASMはもう使えますか?

残念ながら、Edgeはまだ対応してないです。

WASMを使うなら、JSのバージョンも提供しないといけません。

asm.jsとはどう違う?

  • asm.jsは最適化可能のJavaScriptのサブセットです
  • C/C++のコンパイルターゲットとして開発されました
  • AoT(Ahead-of-time)コンパイル可能です。普通のJavaScriptはJITコンパイラーで実行しながら機械語にコンパイルしています。
  • バイナリーフォーマットではないので、wasmよりファイルサイズが大きい
  • EDGEで使えます
  • http://caniuse.com/#feat=asmjs

CでWASMを書いてみよう

CでWASMを書くにはコンパイラーが必要

EmscriptenのCコンパイラー、emccを使用します。

Emscriptenのビルドは数時間かかります。

使い方は簡単です

emcc -o hello.js hello.c -s WASM=1 -s EXPORTED_FUNCTIONS="['_hello']"
#include <stdio.h>

void hello() {
  printf("Hello, world!\n");
}

-s WASMをつけないとasm.jsを出します

JavaScriptでC関数を呼び出す

Module.ccall(
  'hello', // 関数名
  null, // 返却値の型
  [], // 引数の型
  [], // 引数の値
);

cwrapで同じように関数のラッパーを作成できます。

<html>
  <head>
    <title>Hello, world!</title>
  </head>
  <body>
    <p>Check the log</p>
    <script>
      var Module = {
        onRuntimeInitialized: function() {
          Module.ccall('hello', null, [], []);
        },
      };
    </script>
    <script src="hello.js"></script>
  </body>
</html>

JSのメモリー管理

Emscriptenのランタイムは便利関数をたくさん定義してくれています。

文字列の処理

#include <string.h>

void upper(char *dst, char *src) {
  int i;
  int l = strlen(src);

  for (i = 0; i < l; i++) {
    char c = src[i];

    if (c >= 'a' && c <= 'z') {
      dst[i] = c - ('a' - 'A');
    } else {
      dst[i] = c;
    }
  }

  dst[l] = '\0';
}

文字列の処理

  function handleInput(event) {
    const value = event.target.value;
  
    // メモリを割り当てる
    const src = allocate(intArrayFromString(value), 'i8', ALLOC_NORMAL);
    const dst = _malloc(value.length + 1);
  
    Module.ccall(
      'upper',
      null,
      ['number', 'number'],
      [dst, src],
    );
  
    const result = Pointer_stringify(dst);
  
    _free(src); // メモリリークを起こさないようにメモリ解放
    _free(dst);
  
    output.innerHTML = result;
  }

リアルタイムの画像処理

Sobelフィルター

エッジ検出

画像の勾配のマグニチュードを計算します。畳み込みなので結構重い処理です!

void sobel(unsigned char *dst, unsigned char *src, int width, int height) {
  int pixelX, pixelY, mag;

  for (int i = 1; i < height - 1; i = i + 1) {
    for (int j = 1; j < width - 1; j = j + 1) {
      pixelX = (-1 * getPixel(src, i - 1, j - 1, width)) +
        (1 * getPixel(src, i - 1, j + 1, width)) +
        (-2 * getPixel(src, i, j - 1, width)) +
        (2 * getPixel(src, i, j + 1, width)) +
        (-1 * getPixel(src, i + 1, j - 1, width)) +
        (1 * getPixel(src, i + 1, j + 1, width));

      pixelY = (-1 * getPixel(src, i - 1, j - 1, width)) +
        (-2 * getPixel(src, i - 1, j, width)) +
        (-1 * getPixel(src, i - 1, j + 1, width)) +
        (1 * getPixel(src, i + 1, j - 1, width)) +
        (2 * getPixel(src, i + 1, j, width)) +
        (1 * getPixel(src, i + 1, j + 1, width));

      mag = ceil(sqrt(pixelX * pixelX + pixelY * pixelY));

      if (mag > 255) {
        mag = 255;
      }

      dst[i * 4 * width + 4 * j] = mag;
      dst[i * 4 * width + 4 * j + 1] = mag;
      dst[i * 4 * width + 4 * j + 2] = mag;
      dst[i * 4 * width + 4 * j + 3] = 255;
    }
  }
}

自動最適化

最適化のオプションを指定できます。

emcc -o sobel.js sobel.c -lm -O3 -s WASM=1 -s EXPORTED_FUNCTIONS="['_sobel']"
  • -O1 基本的な最適化
  • -O2 ほとんどの最適化を行います
  • -O3 関数をインラインする。ファイルサイズが大きくなる
  • -Os ファイルサイズを大きくする最適化をしない

検証結果

Emscriptenが出しているasm.jsのパフォーマンスはWASMとあまり変わらないです。

wasmのパフォーマンスは自分で書いたJSより2倍以上速いです。

初期ロードはasm.jsとwasmはあまり変わらないですが、もっと大きいプロジェクトだと違いが出ると思います。

SIMDについて

  • 略してSingle Instruction Multiple Data
  • 一つの命令で複数のデータを処理する
  • 例えば、2 * [1, 2, 3, 4] = [2, 4, 6, 8]
  • シンプルのデータ処理ではパフォーマンス2-4倍アップできる
  • SIMD.jsというのがあったが、ChromeがSIMD.jsを決してコードベースを10%減らしました
  • ChromiumチームによるとwasmにしかSIMDを対応しません。

ご静聴ありがとうございます

Improve Web Application Performance using WASM

By Andreas A

Improve Web Application Performance using WASM

  • 740