読者です 読者をやめる 読者になる 読者になる

第0回 東京 Web PerformanceでSIMD.jsを味見しました

こんにちは、最近隔週でやっている社内週末ハッカソンでElixirとGroovyを触ってニヤニヤしているプログラミング言語好きのedvakfです。次はClojureをやってみたいです。

f:id:devpixiv:20140325045217p:plain

弊社で開かれた第0回 東京 Web Performance - SIMD.jsを味見するというイベントで、SIMD.jsに触ってきました。

SIMD.jsはJavaScriptで高速なベクトル演算ができるAPIです。Firefox NightlyやMicrosoft Edgeで試せるそうです。ほとんど情報が無いので、集まった各自がググりながら使い方を調べてね♪というかなりストイックな集まりでした。

MDNに記事があるのですが、引数が書いてなかったり、現在のFirefoxのNightlyとAPIの乖離があったりして、かなりアルファな匂いがします。

(現在のところの)使い方はこんな感じです。

var a = SIMD.Float32x4(1.0,2.0,3.0,4.0);
var b = SIMD.Float32x4(5.0,6.0,7.0,8.0);
var c = SIMD.Float32x4.add(a,b);
console.log(SIMD.Float32x4.extractLane(c, 0)); // 6
console.log(SIMD.Float32x4.extractLane(c, 1)); // 8
console.log(SIMD.Float32x4.extractLane(c, 2)); // 10
console.log(SIMD.Float32x4.extractLane(c, 3)); // 12

もちろんJavaScriptのTypedArrayを使うこともできます。

var N = 16;
var ary = new Int32Array(N);
for (var i = 0; i < N; i++) {
  ary[i] = i;
}

var a = SIMD.Int32x4.load(ary, 0); // aryの0-3番目の4つの値をaにロードする
var b = SIMD.Int32x4.load(ary, 4); // aryの4-7番目の4つの値をbにロードする
var c = SIMD.Int32x4.add(a, b); // aとbのそれぞれの値を足し合わせる
SIMD.Int32x4.store(ary, 8, c); // aryの8-11番目にcの4つの値を書き込む

手を動かせる時間は1時間ぐらいしかなかったので時間内にギリギリ終わりませんでしたが、waifu2xのJavaScript実装を高速化してみました。

こんな感じで、画像ファイルを選択すると綺麗な画像を作ってくれます。

f:id:edvakf:20150728223448p:plain

ソースコードを見ると、こんな感じになっていたので、

var sumConvolve3x3s = function (src2ds, mats) {
    "use strict";

    var w = src2ds[0].w|0, h = src2ds[0].h|0;
    var rw = w - 2|0, rh = h - 2|0;
    var r = new Float32Array(rw * rh|0);
    var o00 =       0 |0, o01 =       1 |0, o02 =       2 |0,
        o10 =   w + 0 |0, o11 =   w + 1 |0, o12 =   w + 2 |0,
        o20 = 2*w + 0 |0, o21 = 2*w + 1 |0, o22 = 2*w + 2 |0;

    for (var si = 0, sl = src2ds.length|0; si < sl; si++) { 
        var s = src2ds[si].a, m = mats[si];
        var m0 = m[0], m1 = m[1], m2 = m[2];
        var m00 = m0[0], m01 = m0[1], m02 = m0[2],
            m10 = m1[0], m11 = m1[1], m12 = m1[2],
            m20 = m2[0], m21 = m2[1], m22 = m2[2];
        for (var y = 0; y < rh; y++) {
            var yrw = y * rw |0, yw = y * w |0;
            for (var x = 0; x < rw; x++) {
                var ri = yrw + x |0, i = yw + x |0;
                r[ri] += 
                    m00 * s[o00+i|0] + m01 * s[o01+i|0] + m02 * s[o02+i|0] +
                    m10 * s[o10+i|0] + m11 * s[o11+i|0] + m12 * s[o12+i|0] +
                    m20 * s[o20+i|0] + m21 * s[o21+i|0] + m22 * s[o22+i|0];
            }
        }
    }
    
    return {a: r, w: rw, h: rh};
};

こんなふうにしてみると、

var sumConvolve3x3s = function (src2ds, mats) {
    "use strict";

    var w = src2ds[0].w|0, h = src2ds[0].h|0;
    var rw = w - 2|0, rh = h - 2|0;
    var r = new Float32Array(rw * rh|0);
    var o00 =       0 |0, o01 =       1 |0, o02 =       2 |0,
        o10 =   w + 0 |0, o11 =   w + 1 |0, o12 =   w + 2 |0,
        o20 = 2*w + 0 |0, o21 = 2*w + 1 |0, o22 = 2*w + 2 |0;

    for (var si = 0, sl = src2ds.length|0; si < sl; si++) { 
        var s = src2ds[si].a, m = mats[si]; // sはFloat32Array
        var m0 = m[0], m1 = m[1], m2 = m[2];
        var m00 = m0[0], m01 = m0[1], m02 = m0[2],
            m10 = m1[0], m11 = m1[1], m12 = m1[2],
            m20 = m2[0], m21 = m2[1], m22 = m2[2];

        var simd_m00 = SIMD.Float32x4(m00,m00,m00,m00);
        var simd_m01 = SIMD.Float32x4(m01,m01,m01,m01);
        var simd_m02 = SIMD.Float32x4(m02,m02,m02,m02);
        var simd_m10 = SIMD.Float32x4(m10,m10,m10,m10);
        var simd_m11 = SIMD.Float32x4(m11,m11,m11,m11);
        var simd_m12 = SIMD.Float32x4(m12,m12,m12,m12);
        var simd_m20 = SIMD.Float32x4(m20,m20,m20,m20);
        var simd_m21 = SIMD.Float32x4(m21,m21,m21,m21);
        var simd_m22 = SIMD.Float32x4(m22,m22,m22,m22);

        for (var y = 0; y < rh; y++) {
            var yrw = y * rw |0, yw = y * w |0;

            var X = Math.floor(rw / 4)*4 |0;

            for (var x = 0; x < X; x+=4) { // 4つずつ一気に計算
                var ri = yrw + x |0, i = yw + x |0;

                var simd_o00 = SIMD.Float32x4.load(s, o00+i|0);
                var simd_o01 = SIMD.Float32x4.load(s, o01+i|0);
                var simd_o02 = SIMD.Float32x4.load(s, o02+i|0);
                var simd_o10 = SIMD.Float32x4.load(s, o10+i|0);
                var simd_o11 = SIMD.Float32x4.load(s, o11+i|0);
                var simd_o12 = SIMD.Float32x4.load(s, o12+i|0);
                var simd_o20 = SIMD.Float32x4.load(s, o20+i|0);
                var simd_o21 = SIMD.Float32x4.load(s, o21+i|0);
                var simd_o22 = SIMD.Float32x4.load(s, o22+i|0);

                var simd_mo00 = SIMD.Float32x4.mul(simd_o00, simd_m00);
                var simd_mo01 = SIMD.Float32x4.mul(simd_o01, simd_m01);
                var simd_mo02 = SIMD.Float32x4.mul(simd_o02, simd_m02);
                var simd_mo10 = SIMD.Float32x4.mul(simd_o10, simd_m10);
                var simd_mo11 = SIMD.Float32x4.mul(simd_o11, simd_m11);
                var simd_mo12 = SIMD.Float32x4.mul(simd_o12, simd_m12);
                var simd_mo20 = SIMD.Float32x4.mul(simd_o20, simd_m20);
                var simd_mo21 = SIMD.Float32x4.mul(simd_o21, simd_m21);
                var simd_mo22 = SIMD.Float32x4.mul(simd_o22, simd_m22);

                var simd_r = SIMD.Float32x4.add(SIMD.Float32x4.add(
                    SIMD.Float32x4.add(
                        SIMD.Float32x4.add(simd_mo00, simd_mo01),
                        SIMD.Float32x4.add(simd_mo02, simd_mo10)),
                    SIMD.Float32x4.add(
                        SIMD.Float32x4.add(simd_mo11, simd_mo12),
                        SIMD.Float32x4.add(simd_mo20, simd_mo21))
                ), simd_mo22);

                SIMD.Float32x4.store(r, ri, simd_r);
            }
            for (var x = X; x < rw; x++) { // 余りは元の実装にフォールバック
                var ri = yrw + x |0, i = yw + x |0;

                r[ri] += 
                    m00 * s[o00+i|0] + m01 * s[o01+i|0] + m02 * s[o02+i|0] +
                    m10 * s[o10+i|0] + m11 * s[o11+i|0] + m12 * s[o12+i|0] +
                    m20 * s[o20+i|0] + m21 * s[o21+i|0] + m22 * s[o22+i|0];
            }
        }
    }
    
    return {a: r, w: rw, h: rh};
};

実行時間が半分になりました!

f:id:edvakf:20150728224147p:plain

しかし、なぜか画像が暗くなっている…!!今日のところは満足しちゃったので、気が向いたら深追いしてみようとおもいます。

「東京 Web Performance」ではこれからも10人以下ぐらいしか集まらないニッチなウェブパフォーマンス技術に関するハンズオンをやっていくそうです。次回はWebAssemblyかも(?)という声も挙がっていましたが、まだまだネタは募集中だそうです。是非@_furoshikiまでネタを提供してあげてください。