はじめに
今回はforeachとfor文についての記事を書いていきたいと思います。
for
文とforeach
にはそれぞれ特徴があり、使い分けが必要ですよね。
確か私はfor
文は高速だけど、foreach
は可読性が高いといった教え方をされたような気がします。
ただふと疑問に思ったのです、「本当にfor文の方が高速なのか?」と。
というわけで実験をしていきます。
環境
Unity2019.2.11f1
※普段UnityでC#を使うので、そちらでのパフォーマンスです。
前提
最初にややネタバレちっくですが、for
文とforeach
の機能はまったく違います。
・for文:ある条件が成立するまで繰り返す
・foreach文:ある列挙インターフェイスが列挙する要素を繰り返して1つ1つ取得する
foreach
はIEnumerable
インターフェイス(厳密にはGetEnumerator
メソッド)を実装していなければなりません。
そしてGetEnumerator
メソッドの返り値はEnumerator
構造体(IEnumerator<T>
とIEnumerator
を実装)なので、MoveNext
メソッドとCurrent
プロパティを実装、つまりは次のデータと現在のデータしか保持していないのです。(IDisposable
,Reset
メソッドもありますが省略します)
実験①
まずは普通にシンプルな勝負をしましょう。
using System.Collections.Generic; using UnityEngine; using System.Linq; public class Test : MonoBehaviour { private void Start() { List<int> target = Enumerable.Range(0, 10000000).ToList(); int sum = 0; System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); for(int i = 0; i < target.Count; i++) { sum += target[i]; } sw.Stop(); Debug.Log($"for: { sw.ElapsedMilliseconds }ms"); sw.Reset(); sum = 0; sw.Start(); foreach(var item in target) { sum += item; } sw.Stop(); Debug.Log($"foreach: { sw.ElapsedMilliseconds }ms"); } }
結果は…
名前 | 処理時間(ms) |
---|---|
for | 485 |
foreach | 528 |
さすがfor文
、foreach
よりも早い速度を出してきました。
foreachが勝つとき
ただしランダムアクセス性がない(シーケンシャルアクセス)コレクションのときは結果が変わります。
ランダムアクセス性がないとは、numbers[1]
みたく普通には要素番号を指定して要素を取得したりできないことです。例えばIEnumerable<T>
ですね。
これを要素番号を指定するとなると、Linq
のElementAt
(IEnumerableの拡張メソッド)を使うしかないでしょう。
using UnityEngine; using System.Linq; public class Test : MonoBehaviour { private void Start() { var numbers = Enumerable.Range(0, 10000000); int sum = 0; System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); for(int i = 0; i < numbers.Count(); i++) { sum += numbers.ElementAt(i); } sw.Stop(); Debug.Log($"for: { sw.ElapsedMilliseconds }ms {sum}"); sw.Reset(); sum = 0; sw.Start(); foreach(var item in numbers) { sum += item; } sw.Stop(); Debug.Log($"foreach: { sw.ElapsedMilliseconds }ms {sum}"); } }
名前 | 処理時間(ms) |
---|---|
for | 1578 |
foreach | 539 |
foreach
は先程とほぼ変わりませんが、for
文は一気に遅くなってしまいましたね。
一応以下のようにコードを変更すれば少し早くなりますが、やはりforeach
には勝てません。
int count = numbers.Count(); for(int i = 0; i < count; i++) { sum += numbers.ElementAt(i); }
名前 | 処理時間(ms) |
---|---|
for | 1220 |
foreach | 530 |
for文を魔改造
ただし、さらに工夫を重ねればforeach
と並ぶことができます。
using UnityEngine; using System.Linq; public class Test : MonoBehaviour { private void Start() { var numbers = Enumerable.Range(0, 10000000); int sum = 0; System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); var enumerator = numbers.GetEnumerator(); for(;enumerator.MoveNext();) { sum += enumerator.Current; } sw.Stop(); Debug.Log($"for: { sw.ElapsedMilliseconds }ms {sum}"); sw.Reset(); sum = 0; sw.Start(); foreach(var item in numbers) { sum += item; } sw.Stop(); Debug.Log($"foreach: { sw.ElapsedMilliseconds }ms {sum}"); } }
名前 | 処理時間(ms) |
---|---|
for | 576 |
foreach | 547 |
ほとんど同じ値にまで近づけることができました。
ただこのfor文
の中身は、もはやforeach
の内部的な仕組みとほぼ同じになっています。
わざわざこんなコードを書くなら、foreach
を書いたほうがよいでしょう。
さいごに
少しはforeach
の名誉を守りましたが、速度的にはfor文
の方が早い場合が多いと思います。
ただ、やはり可読性などの面からもfor
,foreach
をうまく使い分けていきたいですね!