高速化
昨日がベンチマークだったので、今日は速度について。
いつまで教科書を信じてるんだい?
プログラミングの教本には、「ループ」がこのように書かれています。
これはよく見られる記述ですが、最も遅い書き方です。
var ary = [...]; var i; for (i = 0; i < ary.length; i++) { なにか }
ループ毎にlengthを再評価しなければ、ちょっと早くなります。
var ary = [...]; var i; var sz = ary.length; for (i = 0; i < sz; i++) { なにか }
i++ を ++i にするとさらに早くなります。
var ary = [...], i = 0, sz = ary.length; for (; i < sz; ++i) { なにか }
これらの些細な修正を施すだけで、単純ループなら5〜8%は高速化する可能性があります。
なぜ早くなるのか理解できない人は、これを機会に、i++ と ++i をハンドアセンブルしてみると良いでしょう。開発者としてのレベルが3つぐらい上がるはずです。
== と ===
厳密比較演算子を搭載している言語では、== より === で比較するとより高速です。
== はあいまいな比較を行います。答えが見つかるまでフラフラと内部で型を類推(変換)し比較し続けます。
=== は1回しか比較を行いません。内部で型の類推(変換)をしません。
=== を使いましょう。それだけで早くなります。PHP等もこれで早くなります。
ループが展開できたとしても、正解はそれだけではない
ループをさらに高速化する手法として「ループ展開」があります。これは20年以上前から多用されている古典的な手法の一つです。
500回のループなら、496回分(8*62)のループブロックと、4回分の通常のループに分けて処理します。
分母を8から16にするとさらに高速化できる場合があります。
Array.prototype.forEach = function(fn, me) { var i = -1, sz = this.length, r = (sz % 8) + 1; while (++i, --r) { (i in this) && fn.call(me, this[i], i, this); } if ( (r = (sz >>> 3) | 0) ) { // ( strict ) guard --i, ++r; while (--r) { (++i in this) && fn.call(me, this[i], i, this); (++i in this) && fn.call(me, this[i], i, this); (++i in this) && fn.call(me, this[i], i, this); (++i in this) && fn.call(me, this[i], i, this); (++i in this) && fn.call(me, this[i], i, this); (++i in this) && fn.call(me, this[i], i, this); (++i in this) && fn.call(me, this[i], i, this); (++i in this) && fn.call(me, this[i], i, this); } } };
しかし、ループ展開は万能ではありません。
2〜10回程度の小さな(日常的な)ループでは、オーバーヘッドがあるため逆に遅くなるケースがあります。