canvasをより速く(Flashもサポート)-Take4

このエントリは
canvasをより速く(Flashもサポート) - latest log
canvasをより速く(Flashもサポート)-Take3 - latest log の続きです。

今日も、Flashモードのレンダリングをちょっと速くしました。

送信部分の高速化

修正前

var _stack = []; 
var _lockState = 0; 
var _readyState = 0; // js側初期化済で1, Flash側初期化済で2 

function send(fg) { // @param String: fragment, "{COMMAND}\t{ARG1}\t..."
    if (fg) {
        this._stock.push(fg);
    }
    if (!this._lockState && this._readyState) {
        if (this._readyState === 2) {
            var ctx = this;

// ▼▼▼▼▼▼▼ ここから修正 ▼▼▼▼▼▼▼
            // <param name="flashVars" param="t={time}&c={cmd}" />
            setTimeout(function() {
                if (ctx._stock.length) {
                    ctx._view.flashVars = "t=" + (+new Date) +
                                         "&c=" + ctx._stock.join("\t");
                    ctx._stock = []; // clear
                }
            }, 0);
// ▲▲▲▲▲▲▲ ここまで修正 ▲▲▲▲▲▲▲
        }
    }
}

修正後

  • ctx._tmid に setTimeoutの戻り値を保存
    • ctx._tmid が真なら送信待ちのキューが存在することになる
      • send()が呼ばれるたびにキューを作るのではなく、既に送信待ちのキューがあれば、それに便乗できるので、新たにsetTimeoutでキューを作成しないように変更
  • +new Date で時刻を取得し送信していたのを止めた
    • 適当にユニークな値をFlash側に送れればいいので、IEでコスト高な +new Date ではなく、1〜9 でグルグルする値(ctx._msgid)に切り替え
            // <param name="flashVars" param="t={time}&c={cmd}" />
            if (!ctx._tmid) {
                ctx._tmid = setTimeout(function() { // タイマーIDを保存
                    if (ctx._tmid && ctx._stock.length) {
                        ctx._view.flashVars = "i=" + ++ctx._msgid + // タイムスタンプではなくIDを送信
                                             "&c=" + ctx._stock.join("\t");
                        ctx._stock = []; // clear
                        ctx._msgid > 9 && (ctx._msgid = 0); // IDの値は 1〜9 まで
                        ctx._tmid = clearTimeout(ctx._tmid);
                    }
                }, 0);
            }

送信データの圧縮

ctx.moveTo(x,y) や ctx.lineTo(x,y) で指定する座標(x, y)は、ほとんどのケースで 0.1234567891234 のような半端な数値だったりします。
今までは小数点以下も含め全て送信していましたが、ちっさい数値は丸々無駄なデータなので、小数点以下は適当な桁(2〜3桁)でちょんぎって送る事に。

普通に考えると、Number.toFixed(3) なのですが、今は速度が必要なので、関数は極力呼ばずに何とかする必要があります。

100倍して切り捨てて文字列を送って、1/100する

toFixed のコストを測定した結果、moveTo, lineTo の値を加工(100倍 + 切り捨て + 文字列化)し、Flash側で数値に戻す際に1/100にすると toFixed を使った場合に比べ、1.5倍速くなることがわかりました。

JavaScript 側のコマンドエンコーダ

// CanvasRenderingContext2D.prototype.lineTo
function lineTo(x, y) {
    this.send("lT\t" + ((x * 100) | 0) + "\t" + ((y * 100) | 0)); // 100倍して端数を切り落とす
}
// CanvasRenderingContext2D.prototype.moveTo
function moveTo(x, y) {
    this.send("mT\t" + ((x * 100) | 0) + "\t" + ((y * 100) | 0)); // 0.12345... を "12" にエンコード
}

Flash 側のコマンドデコーダ

private function recv(msg:String):void {
  var ary:Array = msg.split("\t");
  var i:int = -1;
  var iz:int = ary.length;

  while (++i < iz) {
    switch (ary[i]) { // {COMMAND}
    case "mT": moveTo(ary[++i] * 0.01, ary[++i] * 0.01); break; // "12" を 0.12 にデコード
    case "lT": lineTo(ary[++i] * 0.01, ary[++i] * 0.01); break;
    }
  }

精度が必要になったら有効桁数を3〜4桁に増やすこともそんなに手間では無いので、割と良いアイデアじゃないかと思ってます。

デモ - 2/15版

Flash モードが速くなり、幾つかのページにストレスボタンを追加しています。

なんだかんだで、毎日ちょっとずつ速くなってます。

Flashモードは4日前まで 20fps がやっとだったけど、今は元気に 60fps で動いてます。

何で纏めて記事にしないの? とか

こういった、ジリジリと高速化していくネタは「がんばった。速くなった。これこれやった。はい終わり」のような過去形ではなく、現在進行形で書いたほうが良いかなぁ〜 とか思ってやってますよ。