最適化前後のコードを一緒に配布し技術共有を促進する(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 という名前で提供すると、何処をどういじって速くしたのか一目瞭然になるので、最適化後のソースコードだけを配布するよりも、分かりやすいかなぁ〜 とか思いましたよ。