今回試したコードは、以下のようなものです。
const int Max = 10_000_000; var stopwatch = Stopwatch.StartNew(); int result = 0; foreach(var i in Enumerable.Range(0, Max)) result += i; stopwatch.Stop(); Console.WriteLine("foreach Result: {0}/{1}ms", result, stopwatch.ElapsedMilliseconds); var stopwatch2 = Stopwatch.StartNew(); int result2 = 0; for(int i = 0; i < Max; ++i) result2 += i; stopwatch2.Stop(); Console.WriteLine("for Result: {0}/{1}ms", result2, stopwatch2.ElapsedMilliseconds);
Stopwatchインスタンスを生成して、foreach/forループ内で足しこむだけです。最後にそれぞれの結果を出力するのは、デッドコード削除で足しこみ自体省略されたら、嫌だなという意図です。このコードだとそれぞれ、確実にオーバーフローが発生しますが、何も書かなければC#はオーバーフローを無視してくれるはずなので、今回は特に考慮していません。さて、この単純なコードで出た結果がこちら。
Debugビルド時 foreach Result: -2014260032/197ms for Result: -2014260032/41ms
Releaseビルド時 foreach Result: -2014260032/171ms for Result: -2014260032/26ms
テスト環境はMacBook Pro (Retina, 13-inch, Early 2015)で2.7Ghz Intel Core i5、メモリ16GBです。Debugビルド時でおよそ5倍、Releaseビルド時で7倍弱程度の差ができてしまいました。これを見る限り、ループごとに一時変数を用意してGC走らせていそうなのは間違いなさそうですね。それにしても、結構インパクトでかいです。そっか〜、foreachって遅かったのか〜。とは言っても、可読性重視したいので、使い続けますけどね。for使うとめんどくさいですしね。
速度にうるさいC++のrange-based forとかはどんな実装になってるんだろう。ループ全体で一時変数を使いまわしたりしてないのかな〜。まあ、そうしたら、この一時変数のスコープが大きくなっちゃうんですがね。
では、また(๑╹ω╹๑ )