質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.10%

for文と Underscore.js の each はどちらが速いですか?

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 2,593

score 35

underscore.js を導入してから、for文を使う機会が減りました。
何故なら、underscore の each がとても便利だからです。

しかし、処理速度はどちらが速いのでしょうか?

for文が速いのなら、for文を極力使いたいですし、
each文が速いのなら、エラー処理も実装できて便利なので、後者を利用します。

以上よろしくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+3

for文と_.eachの処理速度に大差はなく、DOM操作の方が問題になることが多いということでした。
以下ソースです。

foreach vs for loop

For Loop: 0.143s
Underscore.js foreach: 0.171s

https://gist.github.com/cerivera/8534081

_.each vs for

chances are that most of the CPU cost is in DOM manipulation and reflows.

https://samuelrossille.com/posts/2013-02-22-each-vs-for.html

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/22 15:48 編集

    確かにDOM操作は重いですよね。
    私は可能な限り、 Templete を使っています。
    回答ありがとうございました。

    キャンセル

  • 2017/08/22 16:19

    _.each使った方がいいですよ。
    パフォーマンスを常に考える人は問題があった場合、探す力がある人だと思います。
    できる限り書きやすく保守しやすいように書いて問題が起きれば取り組めばいいと思います。
    最近のブラウザのエンジンも高性能になりましたからね。

    キャンセル

  • 2017/08/22 16:21

    可読性も重要だと思うので、 each を今後も使いたいと思います。
    ありがとうございました。

    キャンセル

+3

 while 文

クローズしているようですが、速さを追い求めるなら while 文が最速です。

var bench = new Array(10000000);
var sTime, fTime;

/**
 * for-1
 */
sTime = new Date();

for (var i = 0; i < bench.length; i++) {
    bench[i] = Math.sqrt(Math.pow(12345) + Math.pow(53142));
}

fTime = new Date();

console.log(fTime - sTime); // 結果 -> 718

/**
 * while-1
 */
sTime = new Date();
var i = bench.length;

while (i--) {
  bench[i] = Math.sqrt(Math.pow(12345) + Math.pow(53142));
}

fTime = new Date();

console.log(fTime - sTime); // 結果 -> 577

 更に速く

ただし、前述のコードにはまだ最適化の余地があります。

var bench = new Array(10000000);
var sTime, fTime;

/**
 * for-2
 */
sTime = new Date();

for (var i = 0, len = bench.length, sqrt = Math.sqrt, pow = Math.pow; i < len; i++) {
    bench[i] = sqrt(pow(12345) + pow(53142));
}

fTime = new Date();

console.log(fTime - sTime); // 結果 -> 641

/**
 * while-2
 */
sTime = new Date();
var i = bench.length,
    sqrt = Math.sqrt,
    pow = Math.pow;

while (i--) {
  bench[i] = sqrt(pow(12345) + pow(53142));
}

fTime = new Date();

console.log(fTime - sTime); // 結果 -> 555

ちなみに、最近のブラウザは最適化が進んでいる為、使用するブラウザ、クライアントPCのスペックによっては上記差は小さくなる可能性があります。
しかしながら、原理的には for 文は第一式、第三式の処理が while 文よりも余計にかかっているので while 文の方が速くなります。
第一式はともかく、第二式+第三式が両方とも実行されるのは for 文にパフォーマンス上は不利に働きます。
出来るだけ多くのブラウザで高速に動作させることを求めるならば、while 文の一択だと私は思います。

 比較まとめ

コードの最適化を進めた上で比較してみました。

var bench = new Array(10000000);
var sTime, fTime;

/**
 * _.each
 */
sTime = new Date();
var sqrt = Math.sqrt,
    pow = Math.pow;
_.each(bench, function (value, index) {
    bench[index] = sqrt(pow(12345) + pow(53142));
});
fTime = new Date();
console.log('_.each', fTime - sTime);

/**
 * Array.prototype.forEach
 */
sTime = new Date();
var sqrt = Math.sqrt,
    pow = Math.pow;
bench.forEach(function (value, index, array) {
  array[index] = sqrt(pow(12345) + pow(53142));
});
fTime = new Date();
console.log('Array.prototype.forEach', fTime - sTime);

/**
 * for
 */
sTime = new Date();
for (var i = 0, len = bench.length, sqrt = Math.sqrt, pow = Math.pow; i < len; i++) {
    bench[i] = sqrt(pow(12345) + pow(53142));
}
fTime = new Date();
console.log('for', fTime - sTime);

/**
 * while
 */
sTime = new Date();
var i = bench.length,
    sqrt = Math.sqrt,
    pow = Math.pow;

while (i--) {
  bench[i] = sqrt(pow(12345) + pow(53142));
}

fTime = new Date();
console.log('while', fTime - sTime);

私の環境では、次の結果となりました。

"_.each 899"
"Array.prototype.forEach 658"
"for 574"
"while 570"
  • ライブラリはネイティブ機能に勝てません。
  • 繰り返し処理において、関数呼び出しは制御構文(while, for 等)よりも遅いです。

 更新履歴

  • 2017/08/22 23:40 「比較まとめ」の節を追記

Re: airulove さん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/22 23:21

    回答ありがとうございます!!
    参考にさせて頂きます。

    キャンセル

  • 2017/08/23 09:44

    わたしのところでは、forEach が圧倒的に速かったのですがなぜでしょうか?

    キャンセル

  • 2017/08/23 11:39

    forEach に実装の特別な最適化が働いているのかもしれません。
    私は Google Chrome 60.0.3112.101 で検証しました。

    キャンセル

check解決した方法

+2

簡単な検証をしてみました。
重い計算式が思いつかなかったので、
三平方の定理を使いました。

var bench = new Array(10000000);
var sTime = 0;
var fTime = 0;

sTime = new Date();

for (var i = 0; i < bench.length; i++) {
    bench[i] = Math.sqrt(Math.pow(12345) + Math.pow(53142));
}

fTime = new Date();

console.log(fTime - sTime); // 結果 -> 415

sTime = fTime;

_.each(bench, function (value, index) {
    value = Math.sqrt(Math.pow(12345) + Math.pow(53142));
});

fTime = new Date();

console.log(fTime - sTime); // 結果 -> 1407

3倍以上の差がある結果になりました。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/22 16:27

    確かに 1 年以上更新してないですね。
    最新版入れて確認してみます。

    キャンセル

  • 2017/08/22 16:30

    最新版入れてみましたが結果は変化なしです。

    キャンセル

  • 2017/08/22 23:28

    率直に申し上げますと、_.each のコードは
    value = Math.sqrt(Math.pow(12345) + Math.pow(53142));
    となっていて、配列格納されてなく、for 文と同じ動作になっていないのでフェアな比較ではないと私は思います。
    同じ条件で比較すれば、ライブラリが標準APIより速くなる道理はないと思います。
    特に繰り返し処理に関しては、関数呼び出しと制御構文の間には超えられない壁があるように思います。

    キャンセル

0

どんな処理をするかによって、書き方を含めて色々変わってくるのかと思います。ですので、処理の内容を決めて、いろいろな書き方を試して見ました。

処理内容: ある数までの素数の配列に対して、それぞれの逆数の和を計算します。(数学的には無限大に発散します)

const _ = require('underscore');
const lodash = require('lodash');
const Lazy = require('lazy.js');
const R = require('ramda');
const Benchmark = require('benchmark');

function generatePrimeNumbers(max) {
  const itr23 = (function*() {
    yield 2;
    yield 3;
    let i = 1;
    while (true) {
      i += 4;
      yield i;
      i += 2;
      yield i;
    }
  })();
  const primeNums = [];
  const checkMax = Math.sqrt(max);
  const primeChecks = new Array(max);
  let checkNum = itr23.next().value;
  while (checkNum <= checkMax) {
    if (!primeChecks[checkNum]) {
      primeNums.push(checkNum);
      let targetNum = checkNum * checkNum;
      while (targetNum <= max) {
        primeChecks[targetNum] = true;
        targetNum += checkNum;
      }
    }
    checkNum = itr23.next().value;
  }
  while (checkNum <= max) {
    if (!primeChecks[checkNum]) {
      primeNums.push(checkNum);
    }
    checkNum = itr23.next().value;
  }
  return Object.freeze(primeNums);
}

// pnums = generatePrimeNumbers(100);
// pnums = generatePrimeNumbers(10000);
pnums = generatePrimeNumbers(1000000);
// pnums = generatePrimeNumbers(10000000);

let correctSum = 0;
for (let i = 0, len = pnums.length; i < len; i++) {
  correctSum += 1 / pnums[i];
}
console.log(`sum = ${correctSum}`);

const suite = new Benchmark.Suite;
suite
  .add('for(;;)', () => {
    let sum = 0;
    for (let i = 0, len = pnums.length; i < len; i++) {
      sum += 1 / pnums[i];
    }
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('for of', () => {
    let sum = 0;
    for (let value of pnums) {
      sum += 1 / value;
    }
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('while', () => {
    let sum = 0;
    let i = 0;
    const len = pnums.length;
    while (i < len) {
      sum += 1 / pnums[i];
      i++;
    }
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('forEach', () => {
    let sum = 0;
    pnums.forEach(value => {
      sum += 1 / value;
    });
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('_.each', () => {
    let sum = 0;
    _.each(pnums, value => {
      sum += 1 / value;
    });
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('lodash.each', () => {
    let sum = 0;
    lodash.each(pnums, value => {
      sum += 1 / value;
    });
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('map.reduce', () => {
    const sum = pnums.map(v => 1 / v).reduce((a, b) => a + b, 0);
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('_.map => reduce', () => {
    const sum = _.reduce(_.map(pnums, v => 1 / v), (a, b) => a + b, 0);
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('lodash.map => reduce', () => {
    const sum = lodash.reduce(lodash.map(pnums, v => 1 / v), (a, b) => a + b,
      0);
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('Lazy.map.reduce', () => {
    const sum = Lazy(pnums).map(v => 1 / v).reduce((a, b) => a + b, 0);
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('Ramda map => reduce', () => {
    const sum = R.reduce(R.add, 0, R.map(v => 1 / v, pnums));
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('lodash.map => sum', () => {
    const sum = lodash.sum(lodash.map(pnums, v => 1 / v));
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('Lazy.map.sum', () => {
    const sum = Lazy(pnums).map(v => 1 / v).sum();
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('Ramda map => sum', () => {
    const sum = R.sum(R.map(v => 1 / v, pnums));
    if (sum !== correctSum) throw 'Not correct';
  })
  .add('lodash.sumBy', () => {
    const sum = lodash.sumBy(pnums, v => 1 / v);
    if (sum !== correctSum) throw 'Not correct';
  })
  .on('cycle', function(event) {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  })
  .run({
    'async': true
  });

実行結果

sum = 2.8873280995676938
for(;;) x 184 ops/sec ±5.72% (63 runs sampled)
for of x 56.19 ops/sec ±2.76% (57 runs sampled)
while x 165 ops/sec ±7.68% (61 runs sampled)
forEach x 17.92 ops/sec ±6.76% (33 runs sampled)
_.each x 101 ops/sec ±4.00% (67 runs sampled)
lodash.each x 99.58 ops/sec ±3.65% (69 runs sampled)
map.reduce x 13.17 ops/sec ±6.51% (35 runs sampled)
_.map => reduce x 53.82 ops/sec ±9.73% (55 runs sampled)
lodash.map => reduce x 36.80 ops/sec ±7.56% (40 runs sampled)
Lazy.map.reduce x 64.91 ops/sec ±4.08% (54 runs sampled)
Ramda map => reduce x 54.05 ops/sec ±5.87% (55 runs sampled)
lodash.map => sum x 38.73 ops/sec ±7.35% (40 runs sampled)
Lazy.map.sum x 65.47 ops/sec ±10.10% (57 runs sampled)
Ramda map => sum x 56.32 ops/sec ±4.42% (55 runs sampled)
lodash.sumBy x 79.98 ops/sec ±4.40% (60 runs sampled)
Fastest is for(;;)

【環境】
Windows 10 64bit
Node.js v8.4.0 (64bit)
underscore@1.8.3
lodash@4.17.4
lazy.js@0.5.0
ramda@0.24.1
※ Babel等でのトランスパイルはしていません。

前半のgeneratePrimeNumbersはエラトステネスの篩を使った素数の配列を求める関数です。高速化するためにやけにこだわって作ってみました。この配列はベンチマークで共通で使うため、Obejct.freezeで変更できないようにしています。

後半はBenchmark.jsを使ったベンチマークです。ops/escが高いほど1秒あたりの処理数が多い=速いとなります。

実行タイミングで揺れはありますが、for(;;)が最速でwhileが次点でした。ただ、逆転する場合もありますので、ほぼ同じぐらいとみても良いかもしれません。underscore.jsやlodashのeachはそこそこの速さですが、for(;;)には負けるようです。逆に、ネイティブのはずのforEachはかなり遅いという結果になりました。

map.reduce以降はいわゆる関数型プログラミングの考え方でコーディングした場合です。同じような処理ならLazy.jsが優秀ですが、単純に何かをした和という話ならlodashのsumByが一番良いようです。こちらも、ネイティブのmapとreduceの組合せはとても遅いという結果になりました。

他にも書き方は色々ありますので、試して見ると良いでしょう。

JavaScriptの歴史からすると、ネイティブのforEachやmap、reduceというのはES5からであり比較的最近です。単なるfor(;;)文に比べてJavaScriptエンジン内での最適化がまだまだ甘い可能性はあると思われます。underscore.jsやlodashは速度を高めるためにfor(;;)を内部で使っているという話ですので、呼び出しのオーバーヘッド分ぐらいしか差が出なかったのかも知れません。

なお、この結果は上の【環境】に書いたNode.jsでの話であって、他のJavaScriptエンジンでは変わる可能性が十分にあります。Node.jsのバージョンや各ライブラリのバージョンによっても全く違う結果になる場合もありえますので、ご注意ください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る