ES6速度測定(配列編)

今こんな事やっても気が早いというか、v8のes6まわりの進化に伴ってどうせすぐに結果が変わる。それはわかってるんだけど、ともかく今es6使うから今速度計測してみるのだ。

for-of ループ

配列の総和を求める関数で計測。ソースはこんな感じ

const arr=(function(){
    let i, ret=[];
    for(i=1000;i--;) ret[i]=i;
    return ret;
})();
console.log('arr',arr);

const tests=[
    function() {  // いつものループ
        let i, sum=0, a=arr, n=a.length;
        for(i=0; i<n; i++) sum+=a[i];
        return sum;
    },
    function() {  // for(var i=0;...) を機械的にletに 
        let sum=0, a=arr, n=a.length;
        for(let i=0; i<n; i++) sum+=a[i];
        return sum;
    },
    function() {  // よくある逆順回し
        let i, sum=0, a=arr;
        for(i=a.length;  i-- >0;) sum+=a[i];
        return sum;
    },
    function() {  // for...of登場
        let v, sum=0, a=arr;
        for(v of a) sum+=v;
        return sum;
    },
    function() {  // for...ofでもletしてみる
        let sum=0, a=arr;
        for(let v of a) sum+=v;
        return sum;
    }
]

これを benchmark.js を使って

for(let i=0,n=tests.length; i<n; i++){
    console.log(i+1, tests[i]());
    suite.add('['+(i+1)+']', tests[i]);
}
suite.run({ 'async': true });

で実行。結果は、Chrome 57.0.2987.133(v8 5.8.283.32)では、

[1] x 100,884 ops/sec ±1.39% (62 runs sampled)
[2] x 58,742 ops/sec ±0.94% (62 runs sampled)
[3] x 106,726 ops/sec ±0.84% (63 runs sampled)
[4] x 536,084 ops/sec ±0.85% (63 runs sampled)
[5] x 533,581 ops/sec ±0.99% (63 runs sampled)

Electron 1.6.5(v8 5.6.326.50)では、

[1] x 104,903 ops/sec ±0.50% (89 runs sampled)
[2] x 58,469 ops/sec ±0.61% (87 runs sampled)
[3] x 106,472 ops/sec ±0.78% (85 runs sampled)
[4] x 112,681 ops/sec ±0.61% (88 runs sampled)
[5] x 112,252 ops/sec ±1.25% (84 runs sampled)

となった。 ちなみに手元のnode.jsはv7.9.0でv8のversionは5.5.372.43ともっと古いので計測してない。

for-ofはchromeでは圧倒的。electronでは従来並だけど、v8のバージョンがchromeのものより少し古いせいだとすれば、バージョンアップに伴って速くなるかも。実はこれがトランスパイラなしでes6を使ってみる気になった理由だ。

注意したいのは、2番目のようなletの使い方。ループ内が別スコープになるのでsumなどへのアクセスが遅くなる・・・にしては速度低下が極端な気もするが、ともかく、既存ソースのvarを機械的にletに書き換えたらこの形になる心当たりはありまくる。気をつけよう。 ちなみに5番目の、for-ofでletしたケースでは遅くはなっていない。for-ofでletできる変数って、(実装は知らないけど意味的には)変数というよりも配列各要素のビューだから、上手いことやれるんだろう。

おまけ

遅かった2番目、単一スコープに無理やり押し込んで

function() {
    let o={sum:0};
    for(let i=0, a=arr, n=a.length, s=o; i<n; i++) s.sum+=a[i];
    return s.sum;
}

という形にしたら

[6] x 304,702 ops/sec ±0.76% (63 runs sampled)

なにそれ速い。どゆこと。

spread演算子

一緒くたにしてしまったけど、最初3ケースは配列のシャローコピー、後ろ3ケースは最大値特定処理の計測。

const tests=[
    function(){  // concat()で複製
        return arr.concat();
    },
    function(){  // spread演算子で複製
        return [...arr];
    },
    function(){  // push()にspread
        let ret=[];
        ret.push(...arr);
        return ret;
    },
    function(){  // ループで最大値を求める
        let i, max=arr[0], a=arr, n=a.length;
        for(i=1; i<n; i++) if(a[i]>max) max=a[i];
        return max;
    },
    function(){  // Math.max()にspread
        return Math.max(...arr);
    },
    function(){  // apply()
        return Math.max.apply(Math, arr);
    }
];

結果はChrome

[1] x 907,373 ops/sec ±1.09% (63 runs sampled)
[2] x 21,710 ops/sec ±0.77% (62 runs sampled)
[3] x 318,282 ops/sec ±0.54% (63 runs sampled)
[4] x 882,226 ops/sec ±0.87% (61 runs sampled)
[5] x 338,162 ops/sec ±0.82% (63 runs sampled)
[6] x 313,212 ops/sec ±1.02% (61 runs sampled)

electronで

[1] x 903,084 ops/sec ±0.74% (85 runs sampled)
[2] x 19,549 ops/sec ±0.51% (87 runs sampled)
[3] x 29,599 ops/sec ±0.47% (87 runs sampled)
[4] x 911,266 ops/sec ±0.44% (86 runs sampled)
[5] x 31,114 ops/sec ±0.41% (89 runs sampled)
[6] x 31,028 ops/sec ±0.45% (87 runs sampled)

結局、配列コピーはconcat()が最速で、最大値特定はループで書くのが最速。それらと比べれば、spread演算子は性能面ではイマイチ。Chromeでは、spread演算自体はそこそこのレベルまで高速化されているようだが、[2]の使い方では酷く遅い。electronでは、chromeと性能が1桁違う。地味にapply()も1桁違うところをみると、spread演算子の高速化にあわせてapply()も速くなったという事だろうか。

とにかく一番短い書き方が最速になってほしいものだけど、そう上手くはいかないみたい。