結局JavaScriptの配列ループはどれが一番速いのか
配列(Array
)に繰り返し処理をするにあたって、for
文、while
文、for...of
文、forEach
はどれが一番速いのか検証します。
検証するに至った理由として、日本で実行速度を検証した記事では、for
文が最速という記事が多く見受けらます。例えば以下です。
- 【2021年版】ブラウザ別!JavaScriptでの繰り返しはどれが最速なのか?
- JavaScriptのfor速度比較が意外な結果だったのでシェアしてみる
- https://www.creativevillage.ne.jp/72411
これらの記事は2021年12月5日現在、ループ比較において日本版Google検索上位にあります。
しかし、コンパイラ言語ならともかくインタプリタ言語において、配列の要素に添字でアクセスしなければならないfor
文が早いというのは直感的には納得できませんでした。さらに、パフォーマンス測定サイトのMeasureThat.netではforEach
の方が早いという結果が出ていたのです。
ループの方法による速度差なんて大したことないんだから、好きなのを使ったら良いんだよというのは分かりますが、速度が求められるライブラリを開発するような場面では、ループが更に繰り返し実行されることも多く、少しでも速くしたいものです。
自分が納得できる形で検証してみようと思います。
実行環境
今回の検証では実行環境について細かく分けて測定しません。理由はサーバーかPCかスマホか、どのブラウザか、どのOSか、CPUは何を使っているか、キリが無いからです。その上、最後まで読んでいただければ分かりますが、実行環境で違いがありすぎてやるだけ無意味と考えています。
そのため、私が使っているSurface Pro X(16GB,64bit,ARMベースプロセッサ) Windows 10にて、Node.js(v17.1.0)とEdge(96.0.1054.43)でのみの測定とします。
測定方法は書きますので、他の環境が気になる場合は自身が納得できる形で実行されてみてください。
検証コード
1,000個の配列に対して以下の5つのパターンで繰り返し処理を行います。for
文の場合、添字で配列の値へのアクセスが必要になるため、すべてのループの中に代入式を入れています。console.log
を繰り返している記事も多いのですが誤差が大きくなりそうなので使用しませんでした。
for (var i = 0; i < arr.length; i++) {
var a = arr[i]
}
for (var i = arr.length; i >= 0; i--) {
var a = arr[i]
}
var i = arr.length
while (i--) {
var a = arr[i]
}
for (var v of arr) {
var a = v
}
arr.forEach(v => {
var a = v
})
パフォーマンス測定サイトで検証
まずはお手軽に測定サイトを使ってみます。単位はops/sec
で、数字が大きいほうが速くなります。
MeasureThat.netで測定
他の人が測定した結果でもforEach
が最速と出ているMeasureThat.netで測定してみます。
※++が文字化けしています。
参照:Run results for: for vs while vs for of vs forEach
圧倒的にforEach
が速いという結果です。for of
よりも速いというのは個人的に意外でした。
JSBEN.CHで測定
他のサイトでも測定してみます。
参照:for vs while vs for of vs forEach
またforEach
が速いという結果になりました。for i--
やwhile
よりもfor i++
の方が早いのは最適化されているのでしょうか。
しかしこのサイトではどれも差があまりなく、テストを繰り返すとたまにfor i++
が一番速いという結果になることもあります。複数回実行しての平均は取っていないようです。
JSBench.Meで測定
このサイトではfor i++
が一番速いという結果になりました。MeasureThat.netとは真逆の結果です。
Perflinkで測定
参照:for vs while vs for of vs forEach
このサイトも実行するたびにたまにfor i++
とforEach
が逆転するのですが、forEach
が最速になることが多いです。
何度実行しても変わらないのはfor of
が最も遅いということでした。
パフォーマンス測定サイトでの考察
4つのベンチマークサイトで測定した結果、forEach
が速いことが多いようです。
実行するたびに結果が変わるというのは様々な要因があると思いますので理解できるのですが、平均を取っているMeasureThat.netとJSBench.Meで結果が真逆になるというのが不思議です。
どのサイトを信じたらいいのか全く分かりません。
Benchmark.jsで検証
測定サイトの結果がそれぞれ異なるため、仕方なくライブラリBenchmark.jsを使って測定してみます。単位はops/sec
で、数字が大きいほうが速くなります。
Node.js結果
1回目
for i++ x 890,587 ops/sec ±1.98% (85 runs sampled)
for i-- x 676,696 ops/sec ±1.51% (88 runs sampled)
while i-- x 535,465 ops/sec ±1.66% (84 runs sampled)
for of x 637,016 ops/sec ±1.69% (85 runs sampled)
forEach x 493,769 ops/sec ±54.98% (84 runs sampled)
2回目
for i++ x 921,759 ops/sec ±0.34% (85 runs sampled)
for i-- x 694,308 ops/sec ±0.53% (85 runs sampled)
while i-- x 557,372 ops/sec ±0.35% (88 runs sampled)
for of x 659,756 ops/sec ±0.50% (89 runs sampled)
forEach x 729,502 ops/sec ±53.00% (91 runs sampled)
ソース:ittedev/my_measure/for_vs_foreach/node.js
for i++
が最速でした。1回目と2回目の2つの結果を載せたのは、forEach
の結果にばらつきがあり、2番手になったり最下位になったりしたためです。
Edge結果
for i++ x 1,486,420 ops/sec ±0.57% (66 runs sampled)
for i-- x 990,967 ops/sec ±1.14% (64 runs sampled)
while i-- x 748,789 ops/sec ±1.18% (66 runs sampled)
for of x 988,361 ops/sec ±0.30% (66 runs sampled)
forEach x 2,322,379 ops/sec ±2.02% (65 runs sampled)
ソース:ittedev/my_measure/for_vs_foreach/browser.html
forEach
が圧倒的に速いです。その他の方法の中ではfor i++
が速いです。
Benchmark.jsでの考察
またしても結果が異なりました。さらに、各検証サイトとも異なる結果です。
Node.jsではfor i++
が早く、EdgeではforEach
が早くなります。
実行環境で異なるという結果です。当然、私の環境以外では違う結果になるでしょう。
あとはBenchmark.jsが信用できるかというと、少なくともパフォーマンス測定サイトよりは、実行環境と測定方法が明確になるので信用できると思います。
考察
結果として1位になることがあったのはfor i++
とforEach
です。どちらが上かというと、環境によって異なるため、はっきり言えませんし、今後変わる可能性もあります。
はっきり言えるのはfor i++
が最速とは限らないということです。むしろ、MeasureThat.netと、Benchmark.jsを使ってEdgeで測った結果から、Chromium系ではforEach
のほうが圧倒的に速いことが多かったのです。
C言語等でよく行なわれていたfor
文とwhile
文のi--
を使ったテクニックは期待した結果が得られませんでした。これはfor i++
を使ったときに最適化が行われているのではないでしょうか。あるいはインクリメントとデクリメントでは実行速度が異なるのかもしれません。
また、比較的新しい書き方のfor...of
文は最速にはならないということが分かりました。
for...of
文は配列だけでなくジェネレータやイテレータにも使え、for await...of
というのもあったりして、同期処理もできるため万能ですので、配列に繰り返し処理したい時とは用途が違うと考えたほうがいいですね。
結論
- ループ速度の順序は実行環境で変わる。
- Node.jsで速度を求めているときは自身の環境で測定した方が良いが、面倒なら
for i++
文を使う。 - ブラウザで速度を求めているときは
forEach
を使う。 - それ以外のとき、
for...of
文とforEach
のどちらか迷ったらforEach
を使い、forEach
が使えないときだけfor...of
文を使う。
Discussion
気になったので調べてみたのですが、要素数が大きくなるとforEachのパフォーマンスが著しく悪くなるようです。
自分の環境だと要素数が
40000000
ぐらいからパフォーマンスが悪くなりました。ここまで大きい配列を扱うことはないとは思いますが、大規模なデータを使うときなどforEachを使わないようにしたほうが良さそうです。
以下自分の環境での実行結果です。
コメントありがとうございます!
おお~
私の環境でも同じ結果になりました。
ブラウザは要素数によって実装を変えてるんでしょうか。急に悪くなりました。
要素数が速度にここまで影響してくるとは全く想像していませんでした。
貴重な情報をありがとうございます。
for++のテストコードですが、ループ条件式に
i < arr.length
が入っているのが気になりました。私の環境では、事前に
var l = arr.length
としておくと、おおよそ倍くらいの速度になりました。(「for++の速度、遅いじゃん!」と思った人が見てくれるといいなと思い、コメント残させていただきました)
コメントありがとうございます!
おっしゃる通りです!
事前に長さを変数に入れるかどうかで、最初に測定したサイトでfor++以外の方法との差を埋めるほどの影響が無かったので、そのまま変数に入れない書き方を採用してしまっていました。
プロパティへのアクセス1回とはいえ軽視するべきではありませんでした。