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でキューを作成しないように変更
- ctx._tmid が真なら送信待ちのキューが存在することになる
- +new Date で時刻を取得し送信していたのを止めた
// <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" にエンコード }
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 モードが速くなり、幾つかのページにストレスボタンを追加しています。
- AutoDetect
- Silverlight → Flash → VML 順にバックエンドを探索
- Flash 優先
- Flash → Silverlight → VML 順にバックエンドを探索
- Silverlight 優先
- Silverlight → Flash → VML 順にバックエンドを探索
- VML のみ
- ExplorerCanvas
なんだかんだで、毎日ちょっとずつ速くなってます。
Flashモードは4日前まで 20fps がやっとだったけど、今は元気に 60fps で動いてます。
何で纏めて記事にしないの? とか
こういった、ジリジリと高速化していくネタは「がんばった。速くなった。これこれやった。はい終わり」のような過去形ではなく、現在進行形で書いたほうが良いかなぁ〜 とか思ってやってますよ。