最適化前後のコードを一緒に配布し技術共有を促進する(BaseCode)
数ヶ月前までは、Google Chrome に実装されている JSON よりも、お手製の msgpack.js のほうが速かったりした事もありました。でも気がついたら、いつの間にか Google Chrome 9 dev にぶっちぎられていました。
で、速度差を挽回すべくコードをいじりだして、ふと思ったんです。いじり倒す前のベースとなるコードは取っとくと、後で役に立つんじゃないか? と。速度差の比較材料になるし、ベリファイも取れるし、いいことあるんじゃないかな? と。
たとえば、Hash を MessagePack にエンコードする処理は、以下(↓)のコードが原型になりますが、
// 先にサイズを求める if (Object.keys) { size = Object.keys(mix).length; } else { for (i in mix) { mix.hasOwnProperty(i) && ++size; } } // サイズを含めて符号化 setType(rv, 16, size, [0x80, 0xde, 0xdf]); // エンコード for (i in mix) { encode(rv, i); encode(rv, mix[i]); }
このコードを速く動かそうとすると、こんな(↓)感じに変形させることになります。
// setType で型とサイズを埋め込むはずの場所を pos に保存しておく pos = rv.length; // keep rewrite position // 0x80 を入れておく。0x80 は Hash で 長さゼロを意味する // set default type [0x80 + 0] rv.push(0x80); // Hash の key サイズ用カウンター size = 0; // エンコード for (i in mix) { ++size; encode(rv, i); encode(rv, mix[i]); } // サイズが1以上16未満なら、rv[pos] を上書き // それ以外なら、Array#splice で配列に setType相当のデータを挿入 // rewrite hash type. lazy setType() if (size && size < 16) { rv[pos] = 0x80 + size; } else if (size < 0x10000) { // 16 rv.splice(pos, 1, 0xde, size >> 8, size & 0xff); } else if (size < 0x100000000) { // 32 rv.splice(pos, 1, 0xdf, size >>> 24, (size >> 16) & 0xff, (size >> 8) & 0xff, size & 0xff); }
元のコードは、setType を行うために、for in ループ または Object.keys で key 数を取得してますが、変形後のコードは、setType 相当を遅延実行させることで、前段の key 数を求める空ループをカットしています。
配布物に最適化前のコードと最適化後のコードの両方を含めておくと、いいんじゃないかな?
変形後(最適化済み)のコードは xxxx.js という名前で提供し、元の(遅い)コードは xxxx.base.js という名前で提供すると、何処をどういじって速くしたのか一目瞭然になるので、最適化後のソースコードだけを配布するよりも、分かりやすいかなぁ〜 とか思いましたよ。