2013-04-19

asm.jsを手書きしつつフィボナッチで速度比較をしてみる

asm.jsを触ってみたので所感など。

asm.jsはJavaScriptのサブセットで、限られた型しか使えないが高速に動作する言語との事。とりあえずどの程度速くなるのか、計算量が多くなるfibonacciの実装で試してみた。参考資料はasm.jsの仕様ぐらいしか無かったのでこれを見ながら。

で、実際に書いてみると型がゆるゆるなJavaScriptのイメージは脆くも崩れ去り、厳格な型チェックの世界である事がわかった。コンパイル言語を書いている時の頭に切り換えないと、コンパイルエラーと延々格闘する事になる。まずはasm.jsのコードは次の形式で、module exportする。
1
2
3
4
5
6
7
8
9
10
11
12
function create_my_asm_module(stdlib, foreign, heap) {
  "use asm";
 
  function hoge() {...}
  function fuga() {...}
 
  return {
    hoge: hoge,
    fuga: fuga
  }
}
asm_my_modules = create_my_asm_module(window);
関数の書き型にも決まりがあり、次の順番で記述する必要がある。
1
2
3
4
5
6
function hoge(fuga) {
  // 1)パラメータの型指定
  // 2)変数の初期化
  // 3)処理
  // 4)return句
}
次にasm.jsで書く関数の引数の型、戻り値の型を決める。引数の型はParameter Type Annotationsで記述する。
1
2
3
4
5
6
function calc_tax_included_price(price, tax_rate) {
    price = price|0;      // priceはint
    tax_rate = +tax_rate; // tax_rateはdouble
 
    //略
}
仕様にはintとdoubleしか無いのでどちらかとなる。
関数の戻り値はReturn Type Annotationsで記述する。関数内にreturnが複数個ある場合、それぞれの箇所でreturnの型が異なるとコンパイルできない。
1
2
3
4
return +d;  // double
return i|0; // signed int
return 1;   // double
return;     // void
で、実際にフィボナッチが動くコードと実行結果が以下。実行はAuroraバージョン22.0a2。

function fast_fib_module(stdlib, foreign, heap) {
"use asm";
function fib(n) {
n = n|0;
// nはint(符号の有無が不明の状態)
// 3はfixnum(0~2^31)
// 比較するためにnをシフト演算でunsignedに変換する
if (n >>> 0 < 3) {
return 1|0;
}
// fibの引数の型はintなのでintのサブセットであるsigned intで渡す
return (fib((n-1)|0) + fib((n-2)|0))|0;
}
return {
fib:fib
};
}
fast_fib = fast_fib_module(window).fib;
function slow_fib(n) {
if (n < 3) {
return 1;
}
return slow_fib(n-1) + slow_fib(n-2);
}
function fast_check(n) {
console.time("ASM:fib(" + n + ")");
console.log(fast_fib(n));
console.timeEnd("ASM:fib(" + n + ")");
}
function slow_check(n) {
console.time("NORMAL:fib(" + n + ")");
console.log(slow_fib(n));
console.timeEnd("NORMAL:fib(" + n + ")");
}
view raw fibonacci.js hosted with ❤ by GitHub
[01:40:15.035] fast_check(35)
[01:40:15.037] undefined
[01:40:15.037] ASM:fib(35): タイマー開始
[01:40:15.076] 9227465
[01:40:15.076] ASM:fib(35): 39ms
--
[01:40:21.539] slow_check(35)
[01:40:21.541] undefined
[01:40:21.541] NORMAL:fib(35): タイマー開始
[01:40:26.163] 9227465
[01:40:26.163] NORMAL:fib(35): 4622ms
view raw result hosted with ❤ by GitHub
asm.jsの方が速いのがわかる。といってもこの程度ならまだしも、実際に高速化したい処理を手でasm.jsで書くのは正直厳しいという印象。githubで "use asm"しているコードを探したら行列演算ライブラリが出てきましたが、ヒープ操作している所が全く読めなくてやばい。