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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Node.js

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

JavaScript

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

Q&A

解決済

3回答

9035閲覧

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

murabito

総合スコア108

Node.js

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

JavaScript

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

2グッド

1クリップ

投稿2018/04/08 21:43

編集2018/04/09 00:40

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
miyabi-sun, m.ts10806👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

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のループはどれが一番高速なのか

投稿2018/04/08 23:40

編集2018/04/08 23:57
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

for文が最速

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

投稿2018/04/08 23:53

y_waiwai

総合スコア87719

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

自己解決

最終トライの結果

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

以下は、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/11 22:07

編集2018/04/12 04:54
murabito

総合スコア108

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

defghi1977

2018/04/11 22:17

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

2018/04/11 22:32

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

2018/04/12 02:38

既にBAが決まっていましたが、別の記事で本質問の参考になりそうなコメントを見つけました。 https://qiita.com/Fushihara/items/3f502df21234d7a6efc7#comment-43c83d85d854ba2abb1c この記事のコメント欄のベンチマーク結果です。 http://jsben.ch/uWy28 だいたい想定通りといった感じの結果です。 やはり本質問はconsole.logが駄目な子なので100回実行して統計を取るみたいな検証方法を用いる必要があるようですね。
murabito

2018/04/12 04:57

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

2018/04/12 05:02

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

2018/04/12 05:23 編集

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問