高速化

昨日がベンチマークだったので、今日は速度について。

いつまで教科書を信じてるんだい?

プログラミングの教本には、「ループ」がこのように書かれています。
これはよく見られる記述ですが、最も遅い書き方です。

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回程度の小さな(日常的な)ループでは、オーバーヘッドがあるため逆に遅くなるケースがあります。