JavaScript
Chrome
firefox
ECMAScript
es6

JavaScriptのfor速度比較が意外な結果だったのでシェアしてみる

JavaScriptのfor文、いろいろ記事が出てますがいまいちどれが速いのかよく分かりません。
そこで、自分の環境で比較してみたらすごく意外な結果になったので公開してみます。
何か間違っていたら教えてください。

間違ってました

追記 2018/02/04
検証してくださった方がいらっしゃいました。firefoxではグローバル変数に触ると急激に速度が落ちるみたいです。モジュールで切ればこの現象は起きませんでした。

私の環境でも、手っ取り早く

モジュールを切った.js
{
  const arr = new Array(10000000).fill(0).map((v, i) => i);
  let sum = 0, i = 0;
  const len = arr.length;
  for(; i < len; i += 1) {
    sum += arr[i]
  }
  console.log(sum);
}

を書いたら爆速で実行されました。今まで普段はWebpackに投げてたけどこの記事ではグローバルに置いてしまっていたから全く意識してなかった……。
最近firefoxにブラウザを戻したので引き続き愛用していきたいと思います。

@noxi515さん、ありがとうございました。

TL;DR

  • Chromeでは for 最速。forfor ofreduce < forEach <<< map
  • firefoxでは reduce 最速。reduce <<< foreach < map << for of < for
  • reduce が可読性でも速度面でも強い
  • 外側で宣言したfunction式でもアロー関数ベタ書きでもオブジェクト再生成がなければ速度にはほぼ影響しない

Motivation

for、結局どれで書いたら速いのかいろいろ意見が出ててわからなかったので、今の暫定を決めておきたくテストしてみました。
ブラウザは今手元にあるChromeとfirefoxで動かしてみました。
先人にのっとって、1000万個の連番配列の中身をひたすら足し合わせます。

比較するもの

for

一番普通の for 文です。いかにも速そう。
安全性のため、var は使わず constlet を使用します。

for of

今流行りの for of です。イテレーターが実装されてれば回せる子ですね。
for in は配列のプロパティとかに代入しちゃってるとそれまで列挙されてしまいそもそも役割が違うので今回は除外しました。

forEach

1つずつ要素を取ってきて。引数のコールバックに代入して処理します。
for よりは結構遅そうなイメージ。

map

ES2015の申し子、 map
return で値返せるところがメリットですが、返り値の不要なループの場合はどのぐらいオーバーヘッドが出てしまうのでしょうか。forEach よりはさらに遅そうです。

reduce

map の兄弟ポジションの reduce。配列の値を1つずつ取ってきて何かする、みたいな処理を書くときにすごく楽ですが、コールバックを取るのでちゃんと速度が出るのか不安です。

ソース

5回やって平均を取ります。
コールバック取る系のものは、function式かarrowベタ書きかでどのぐらい変わるかも比較してみました。

source.js
// 初期化
const arr = new Array(10000000).fill(0).map((v, i) => i);
let sum = 0 | 0;

// #1 for
const len = arr.length | 0;
for (let j = 0; j < 5; ++j) {
    sum = 0 | 0;
    console.time('for');
    for (let i = 0 | 0; i < len; i += 1) {
        sum += arr[i];
    }
    console.timeEnd('for');
    console.log(sum);
}

// #2 for of
for (let i = 0; i < 5; ++i) {
    sum = 0 | 0;
    console.time('for of');
    for (const v of arr) {
        sum += v;
    }
    console.timeEnd('for of');
    console.log(sum);
}

// #3-1 forEach(arrow)
for (let i = 0; i < 5; ++i) {
    sum = 0 | 0;
    console.time('forEach(arrow)');
    arr.forEach(v => {
        sum += v;
    });
    console.timeEnd('forEach(arrow)');
    console.log(sum);
}

// #3-2 forEach(pre-defined function)
function addSum(v) {
    sum += v;
}
for (let i = 0; i < 5; ++i) {
    sum = 0 | 0;
    console.time('forEach(function)');
    arr.forEach(addSum);
    console.timeEnd('forEach(function)');
    console.log(sum);
}

// #4-1 map(arrow)
for (let i = 0; i < 5; ++i) {
    sum = 0 | 0;
    console.time('map(arrow)');
    arr.map(v => sum += v);
    console.timeEnd('map(arrow)');
    console.log(sum);
}

// #4-2 map(pre-defined function)
for (let i = 0; i < 5; ++i) {
    sum = 0 | 0;
    console.time('map(function)');
    arr.map(addSum);
    console.timeEnd('map(function)');
    console.log(sum);
}

// #5-1 reduce(arrow)
for (let i = 0; i < 5; ++i) {
    sum = 0 | 0;
    console.time('reduce(arrow)');
    sum = arr.reduce((sum, v) => sum + v, 0 | 0);
    console.timeEnd('reduce(arrow)');
    console.log(sum);
}

// #5-2 reduce(pre-defined function)
function reduceSum(sum, v) {
    return sum + v;
}
for (let i = 0; i < 5; ++i) {
    sum = 0 | 0;
    console.time('reduce(function)');
    sum = arr.reduce(reduceSum);
    console.timeEnd('reduce(function)');
    console.log(sum);
}

実行環境

  • Windows 10 Home (64bit)
  • Intel Core i7 870 @ 2.93GHz
  • 12GB RAM
  • Chrome 64.0.3282.140
  • firefox 58.0.1

結果

平均 Chrome Firefox
for 249.5 8311.7
for of 272.7 4502.6
forEach(arrow) 424.5 2358.3
forEach(function) 424.0 2338.1
map(arrow) 3118.2 2729.0
map(function) 3251.3 2705.3
reduce(arrow) 270.3 41.4
reduce(function) 272.9 42.0

えぇ……

Chromeでは予想通り for が最速ですね。 for ofreduce もほぼ誤差の範囲内なので、可読性を考えると for ofreduce を使うのが得策のように思えます。

反して、firefoxではなんと reduce(arrow) が最速という結果に。そんなバカな。
というか for 一番遅いじゃん……。

どうしてこうなった

いや、さすがに reduce の方が速いとか普通に考えてありえないので、さすがに何か間違ってると思います。
書き方とかにコツがある可能性がありますが、今の私の技術力だとちょっとわかりません。
こんな結果出されると、console.time が信用できないです。
ソースとか読めば何か分かるのかも。

結論

reducefor of は可読性と速度を両立してるのでこれからは積極的に使っていきましょう。
console.time 信用できない。