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

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

ただいまの
回答率

90.51%

  • JavaScript

    19307questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • Node.js

    2236questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScriptのforとForEachのパフォーマンスについて

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,688

murabito

score 22

forとforEachのパフォーマンスの違いや、配列に値を入れてメモリを確保した状態と、そうでない場合のパフォーマンスの違いを検証してみようと思い、以下のようなコードを書いて実行してみました。

しかし、結果は誤差レベルであったり、むしろ、forEachの方がforよりも早い場合が実行する時によっては、あったりなかったりでした。

for文が最速ではなかったのですか?

僕の検証方法がおかしいのでしょうか?それとも、こういうものですか?

ターミナルのnode.js環境で実行しました。

 パターン1

配列に値を入れてメモリ確保 & for文内で配列参照せずにログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン1');
    for (let i = 0; i < max; i++) {
        console.log(i);
    }
console.timeEnd('パターン1');

パターン1: 32641.203ms

 パターン2

配列に値を入れてメモリ確保 & for文内で配列参照して配列内の値をログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン2');
    for (let i = 0; i < max; i++) {
        console.log(numbers[i]);
    }
console.timeEnd('パターン2');

パターン2': 32736.931ms

 パターン3

配列に値を入れてメモリ確保 & forEachで配列内の値をログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン3');
    numbers.forEach((v) => console.log(v));
console.timeEnd('パターン3');

パターン3': 32587.072ms

 パターン4

配列使わない & for文内でログ出力

'use strict';
var max = 1000000;

console.time('パターン4');
    for (let i = 0; i < max; i++) {
        console.log(i);
    }
console.timeEnd('パターン4');

パターン4: 32935.210ms

 ★再トライ

miyabi-sunさんからの回答を参考に再トライしてみました。
※ console.logはそのままで再トライしてしまいました。

結果的に実行する度にどれが早いか遅いかが誤差レベルで変わってしまうので、for文がforEachより早いというのは分かりませんでした。

 パターン1

配列に値を入れてメモリ確保 & for文内で配列参照せずにログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン1');
    for (let i = 0; i < max; i = (i+1)|0) {
        console.log(i);
    }
console.timeEnd('パターン1');

//パターン1: 32958.586ms

 パターン2

配列に値を入れてメモリ確保 & for文内で配列参照して配列内の値をログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン2');
    for (let i = 0; i < max; i = (i+1)|0) {
        console.log(numbers[i]);
    }
console.timeEnd('パターン2');

//パターン2': 32869.883ms

 パターン3

配列に値を入れてメモリ確保 & forEachで配列内の値をログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン3');
    numbers.forEach((v) => console.log(v));
console.timeEnd('パターン3');

//パターン3': 32855.013ms

 パターン4

配列使わない & for文内でログ出力

'use strict';
var max = 1000000;

console.time('パターン4');
    for (var i = 0; i < max; i = (i+1)|0) {
        console.log(i);
    }
console.timeEnd('パターン4');

//パターン4: 32951.688ms

 ★再々トライ

最後に、miyabi-sunの指摘通り、console.logをprocess.stdout.writeに変えて実行してみました。
また、今回はforEachに渡す関数を匿名関数ではなく、事前に変数に代入してforEachに渡し実行するパターン5を追加しました。

 パターン1

配列に値を入れてメモリ確保 & for文内で配列参照せずにログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン1');
    for (let i = 0; i < max; i = (i+1)|0) {
        process.stdout.write(`${i}\n`);
    }
console.timeEnd('パターン1');

//パターン1: 32088.090ms

 パターン2

配列に値を入れてメモリ確保 & for文内で配列参照して配列内の値をログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン2');
    for (let i = 0; i < max; i = (i+1)|0) {
        process.stdout.write(`${numbers[i]}\n`);
    }
console.timeEnd('パターン2');

//パターン2': 32415.275ms

 パターン3

配列に値を入れてメモリ確保 & forEachで配列内の値をログ出力

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン3');
    numbers.forEach((v) => process.stdout.write(`${v}\n`));
console.timeEnd('パターン3');

//パターン3': 31955.411ms

 パターン4

配列使わない & for文内でログ出力

'use strict';
var max = 1000000;

console.time('パターン4');
    for (var i = 0; i < max; i = (i+1)|0) {
        process.stdout.write(`${i}\n`);
    }
console.timeEnd('パターン4');

//パターン4: 32404.780ms

 パターン5

配列に値を入れてメモリ確保 & forEachで配列内の値をログ出力(事前に関数を変数に代入してforEachに渡す)

'use strict';

var numbers = Array.from({length: 1000000}, (v, k) => k);
var max = numbers.length;

console.time('パターン5');
    const log = (v) => process.stdout.write(`${v}\n`);
    numbers.forEach(log);
console.timeEnd('パターン5');

//パターン5': 32368.805ms
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

+6

JavaScriptでは関数適用は意外とパフォーマンスのダメージは少ないのです。
シンプルな処理ならJITコンパイラが効きますからね。
他の箇所で質問文のコードのパフォーマンスが出ない箇所がありますね。

  • for文内のi++: JSではインクリメントはプラスして式として返す2手必要な単なるエミュレートなので高コスト
  • console.log: 文字列以外を渡してもパースしたり色々してくれるリッチな動作をするのでそれに付随して高コストに

この辺のダメージがデカイんで、質問文の検証は想定とちょっと違う感じになってますね。
まぁ、console.log云々は不甲斐ない速度ですが全部同条件なので影響はしませんけどね。

再テストとして下記を修正してもう一度行ってみてください。

for (var i = 0; i < max; i++)
// ↓
for (var i = 0; i < max; i = (i+1)|0)

console.log
// ↓
process.stdout.write(`numbers[i]¥n`)

参考記事:
JavaScriptのループはどれが一番高速なのか

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

check解決した方法

+1

 最終トライの結果

回答いただいた方にベストアンサーを付けたいところなのですが、こちらの結果をアンサーとした方が質問文の内容を新規に読んだユーザーにとって、コンテンツとして整合性のとれたアンサーとなると判断したため、今回は自己解決ということにさせていただくことにいたしました。

以下は、https://jsperf.com/での検証結果です。

http://jsben.ch/も試そうとしましたが、なぜかエラーが出てしまって上手くテストが実行できなかったのと、jsperf.comと違って、どういったテストエンジンを使っているのかなどの情報が一切公開されていなかったことで利用を控えることに致しました。

※注意1: Webのテストサービスで実行すると時間がえらいかかるため、テストサービスでのテストを複数回繰返したり、ブラウザーを変えて試したり、そもそものテストケースの実行順序を変更することが利用サービスでの結果に影響するのかを検証したりなどは出来ておりません。

※注意2: 質問文内でのテスト結果、この回答に載せているテスト結果が常に正しいことは一切保証致しません。質問文に記載の通り、僕の検証方法自体に誤りがある可能性もあります。

 jsperfでの検証結果

イメージ説明

質問文の最後に追記した「再々トライ」の4番目を除外した全4パターンをconsole.logに置き換えてパフォーマンステストを行った結果となります。

※ 再々テストの4番目を除外した理由は、4番目のものだけ最初のセットアップの部分で配列を作成しており、共通のセットアップ処理をかけないため。

 おまけ1

for文内のi++: JSではインクリメントはプラスして式として返す2手必要な単なるエミュレートなので高コスト

miyabi-sunさんからのご指摘を受けた部分ですが、こちらも別にテストを行ったところ、今回の検証結果では真逆の結果となりました。

イメージ説明

※注意1: Webのテストサービスで実行すると時間がえらいかかるため、テストサービスでのテストを複数回繰返したり、ブラウザーを変えて試したり、そもそものテストケースの実行順序を変更することが利用サービスでの結果に影響するのかを検証したりなどは出来ておりません。

※注意2: 質問文内でのテスト結果、この回答に載せているテスト結果が常に正しいことは一切保証致しません。質問文に記載の通り、僕の検証方法自体に誤りがある可能性もあります。

 おまけ2

こちらの解答欄にmiyabi-sunさんから頂いた別の方のパフォーマンステストをフォークして、テストを実行してみました。

元のテストのコードではforとwhile以外はArrayのpushメソッドをループ内で使っているのに対して、forとwhileは直接、新規の配列に元の配列内の値を代入するというかたちになっていたため、forとwhileの方もArrayのpushメソッドをループ内で用いることで、テスト条件をより近しいものに変更致しました。

その結果、異なる結果が出ています。

イメージ説明
http://jsben.ch/TH50C

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/04/12 07:17

    関連してasm.jsとかwebassemblyとか調べてみると良いよ

    キャンセル

  • 2018/04/12 07:32

    ありがとうございます。web assemblyの方は名前は最近目にするので存在だけは知っていましたが、この機械にasm.jsとwebassemblyをググって少し知識を入れておきました。

    キャンセル

  • 2018/04/12 11:38

    既にBAが決まっていましたが、別の記事で本質問の参考になりそうなコメントを見つけました。
    https://qiita.com/Fushihara/items/3f502df21234d7a6efc7#comment-43c83d85d854ba2abb1c
    この記事のコメント欄のベンチマーク結果です。
    http://jsben.ch/uWy28

    だいたい想定通りといった感じの結果です。
    やはり本質問はconsole.logが駄目な子なので100回実行して統計を取るみたいな検証方法を用いる必要があるようですね。

    キャンセル

  • 2018/04/12 13:57

    > この記事のコメント欄のベンチマーク結果です。
    ありがとうございます。テストに用いられたコードの方を拝見しましたが、テスト条件がfor、whileとそれ以外で変わってしまっていたので、そこを統一したテストコードに直して実行したテスト結果をこちらの回答の追記として加えました。

    キャンセル

  • 2018/04/12 14:02

    mapだけグラフが突き抜けてて草
    mapは配列の要素に関数適用して新しい配列を作り直すという意味の機能なので、これだけ気色が違いますね。
    多分一人だけ配列作ろうと頑張っててやりたいことから逸れてるのが凄まじい時間が掛かった原因のように思えます。

    出力したいという処理に着目する場合はmapはそもそも推奨出来ませんので、
    外しても良いかもしれません。

    キャンセル

  • 2018/04/12 14:20 編集

    forとwhileはArrayの配列メソッド全般とパフォーマンス比較するというよりも、Array.prototype.mapやおそらくArray.prototype.filterと比較すべきものなのでしょうね。それに気づきたことが非常に大きかったです。ただの出力の時は特に気にするほどの差はないので、forでもforEachでも良いとして、パフォーマンスや多めのデータ処理が求められるシーンではforやwhileで、それ以外の通常コーディング時はmapやfilterなどの配列メソッドで良いのかなと。mapだと一桁処理時間変わったのは気になりますが。

    キャンセル

-5

for文が最速

と開発者が言ってるならともかく、
どこの誰ともわからん見も知らん人間が言ってるのを鵜呑みにしてはいけないということで。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • JavaScript

    19307questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • Node.js

    2236questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。