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のメモリー管理
- _malloc(bytes) メモリ割り当て
- _free(ptr) メモリ解放
- Pointer_stringify(ptr) ポインターから文字列を作成
- https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html
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
- 748