在.NET中,哪个循环运行得更快,“for”还是“foreach”?
在C#/ VB.NET / .NET,哪个循环运行更快, for
或foreach
?
自从我读了一个for
循环比foreach
循环更快的工作, 很久以前我就认为它适用于所有集合,泛型集合,所有数组等等。
我搜索了Google,发现了一些文章,但其中大多数是不确定的(阅读文章的评论),并公开结束。
最理想的是将每个场景列出,并为相同的最佳解决方案。
例如(只是一个例子):
- 迭代1000个以上的字符串数组 – 比
foreach
更好 - 用于迭代
IList
(非泛型)字符串 –foreach
比for
更好
在网上找到一些相同的参考资料:
- 由Emmanuel Schanzer创作的盛大的旧文章
- CodeProject FOREACH VS. 对于
- 博客 – 为了达到或不达到
foreach
,这就是问题所在 - ASP.NET论坛 – NET 1.1 C#
for
vsforeach
[编辑]
除了可读性方面外,我对事实和数字也很感兴趣。 有一些应用程序在最后一英里的性能优化挤压很重要。
Patrick Smacchia 在上个月的博客上写道,得出以下结论:
- List上的循环比List上的foreach循环便宜2倍以上。
- 在阵列上循环比在列表上循环便宜2倍左右。
- 因此,使用for循环数组比使用foreach(我相信,这是我们所做的)在List上循环要便宜5倍。
foreach
循环比循环显示更具体的意图 。
使用foreach
循环向任何使用你的代码的人展示了你正在计划对集合中的每个成员做些什么,而不管它在集合中的位置。 它也显示你没有修改原始集合(如果你尝试的话会抛出异常)。
foreach
的另外一个好处是,它可以在任何IEnumerable
,只对IList
,每个元素实际上都有一个索引。
但是,如果你需要使用一个元素的索引,那么当然你应该允许使用一个for
循环。 但是如果你不需要使用索引,那么只有一个就是混乱你的代码。
据我所知,没有显着的性能影响。 在将来的某个阶段,使用foreach
在多核上运行代码可能更容易,但是现在不需要担心。
首先,对德米特里的答案提出反驳。 对于数组而言,C#编译器对于foreach
来说大体上和for
循环相同。 这就解释了为什么这个基准测试的结果基本相同:
using System; using System.Diagnostics; using System.Linq; class Test { const int Size = 1000000; const int Iterations = 10000; static void Main() { double[] data = new double[Size]; Random rng = new Random(); for (int i=0; i < data.Length; i++) { data[i] = rng.NextDouble(); } double correctSum = data.Sum(); Stopwatch sw = Stopwatch.StartNew(); for (int i=0; i < Iterations; i++) { double sum = 0; for (int j=0; j < data.Length; j++) { sum += data[j]; } if (Math.Abs(sum-correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("For loop: {0}", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (int i=0; i < Iterations; i++) { double sum = 0; foreach (double d in data) { sum += d; } if (Math.Abs(sum-correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("Foreach loop: {0}", sw.ElapsedMilliseconds); } }
结果:
For loop: 16638 Foreach loop: 16529
接下来,验证Greg关于集合类型的观点是重要的 – 在上面将数组更改为List<double>
,并得到完全不同的结果。 一般来说,它不仅速度明显较慢,而且foreach比通过索引访问慢得多。 话虽如此,我仍然几乎总是喜欢foreach到一个for循环,它使代码更简单 – 因为可读性几乎总是重要的,而微型优化很少。
任何时候都有性能的争论,你只需要写一个小测试,以便你可以使用定量的结果来支持你的情况。
使用StopWatch类并重复几百万次,以确保准确性。 (没有for循环,这可能很难):
using System.Diagnostics; //... Stopwatch sw = new Stopwatch() sw.Start() for(int i = 0; i < 1000000;i ++) { //do whatever it is you need to time } sw.Stop(); //print out sw.ElapsedMilliseconds
手指越过了这个节目的结果,这种差异是微不足道的,你可能只是做最可维护的代码中的任何结果
它将永远是密切的。 对于一个数组, 有时候会稍微快一点,但是foreach
比较有表现力,并且提供了LINQ等。一般来说,坚持使用foreach
。
另外, foreach
可能会在某些情况下被优化。 例如,索引器可能会使链接列表变得很糟糕,但这可能是快速的。 实际上,由于这个原因,标准LinkedList<T>
甚至不提供索引器。
我的猜测是,在99%的情况下,这可能不会很重要,那么为什么你会选择更快而不是最合适的(如最容易理解/维护)?
两者之间不可能有巨大的性能差异。 一如往常,面对“哪个更快?” 问题,你应该总是认为“我可以测量这个”。
写两个循环,在循环体中执行相同的操作,执行并计时,看看速度的差别。 用一个几乎空的身体和一个类似于你实际做的循环体来做到这一点。 也可以使用您正在使用的集合类型进行尝试,因为不同类型的集合可能具有不同的性能特征。
有很好的理由喜欢 foreach
循环for
循环。 如果你可以使用foreach
循环,你的老板是对的。
但是,并不是每一次迭代都是按顺序依次进行。 如果他禁止的话,那是错的。
如果我是你,我会做的就是把你所有的自然循环变成递归 。 这会教给他,这也是一个很好的心理锻炼。
TechEd 2005的Jeffrey Richter:
“多年来我一直在学习C#编译器,基本上是我的骗子。 “它关乎许多事情。” ..“就像当你做一个foreach循环时……”“……这是你写的一小段代码,但是C#编译器吐出来的东西是非常惊人的。尝试/ finally块,在finally块内部把你的变量转换到一个IDisposable接口,如果转换成功,调用它的Dispose方法,在循环内部它会在循环内重复调用Current属性和MoveNext方法,对象正在被创建下面的很多人使用foreach,因为它是非常简单的编码,非常容易做…“..”foreach是不是很好的性能方面,如果你迭代了一个集合,而不是使用正方形括号符号,只是做索引,这只是更快,它不会在堆上创建任何对象…“
按需网播: http : //msevents.microsoft.com/CUI/WebCastEventDetails.aspx? EventID=1032292286& EventCategory=3& culture= en -US&CountryCode= US
在使用对象集合的情况下, foreach
更好,但是如果增加一个数字, for
循环会更好。
请注意,在最后一种情况下,您可以执行如下操作:
foreach (int i in Enumerable.Range(1,10))...
但是它肯定不会表现得更好,实际上比一个更糟。
这是荒唐的。 没有令人信服的理由禁止循环,性能或其他。
参见Jon Skeet博客的表现基准和其他论点。
这应该可以节省您:
public IEnumerator<int> For(int start, int end, int step) { int n = start; while (n <= end) { yield n; n += step; } }
使用:
foreach (int n in For(1, 200, 4)) { Console.WriteLine(n); }
为了获得更大的胜利,你可以拿三个代表作为参数。
当你循环访问数组,列表等等的常见结构时, foreach
和foreach
循环中的速度差别很小,对集合执行LINQ
查询几乎总是稍微慢一点,尽管写得更好! 正如其他海报所说的,去表现力,而不是毫秒的额外表现。
到目前为止没有说的是,当编译一个foreach
循环时,编译器会根据它正在迭代的集合进行优化。 这意味着当你不确定使用哪个循环时,你应该使用foreach
循环 – 当它被编译时它会为你生成最好的循环。 它也更可读。
foreach
循环的另一个关键优点是,如果你的集合实现改变了(例如从一个int array
到一个List<int>
),那么你的foreach
循环将不需要任何代码改变:
foreach (int i in myCollection)
无论你的集合是什么类型,上面都是一样的,而在你的for
循环中,如果你把myCollection
从一个array
改成一个List
,下面的代码不会生成:
for (int i = 0; i < myCollection.Length, i++)
“有没有什么论据可以帮助我说服for循环可以接受?”
不,如果你的老板是微观管理层来告诉你什么样的编程语言结构使用,那么你真的没有什么可以说的。 抱歉。
这可能取决于您列举的集合的类型和索引器的实现。 一般来说,使用foreach
可能是一个更好的方法。
此外,它将与任何IEnumerable
– 不只是索引器的东西。
每种语言结构都有适当的时间和地点供使用。 有一个原因是C#语言有四个单独的迭代语句 – 每个语句都有特定的用途,并有适当的用法。
我建议你和老板坐下来,理性地解释为什么一个for
循环有一个目的。 有时迭代块比foreach
迭代更清楚地描述一个算法。 当这是真的,使用它们是适当的。
我还要向你的老板指出 – 性能不是,也不应该成为一个问题,更多的是以一种简洁,有意义,可维护的方式表达算法。 像这样的微观优化完全忽略了性能优化的重点,因为任何真正的性能优势将来自算法重新设计和重构,而不是循环重构。
如果经过理性的讨论,还是有这种专制的观点,那么怎么处理呢就由你来决定了。 就个人而言,我不会乐意在理性思考不受欢迎的环境下工作,并会考虑在不同的雇主之下转到另一个职位。 但是,我强烈建议在讨论之前进行讨论 – 这可能只是一个简单的误解。
这是你在影响性能的循环内部做的事,而不是实际的循环构造(假设你的情况是非平凡的)。
是否比foreach
快是真正的重点。 我严重怀疑,选择一个会对你的表现产生重大影响。
优化您的应用程序的最佳方法是通过分析实际的代码。 这将确定占大部分工作/时间的方法。 先优化那些。 如果性能仍然不可接受,请重复此过程。
作为一般规则,我会建议远离微观优化,因为它们很少会产生显着的收益。 唯一的例外是优化识别的热路径(例如,如果您的分析确定了一些高度使用的方法,那么广泛地优化这些方法可能是有意义的)。
这与大多数“哪个更快”的问题具有相同的两个答案:
1)如果你不测量,你不知道。
2)(因为…)这取决于。
这取决于“MoveNext()”方法相对于“this [int index]”方法对于将要迭代的IEnumerable的类型(或多个类型)的代价是多么昂贵。
“foreach”关键字是一系列操作的缩写,它在IEnumerable上调用一次GetEnumerator(),每次迭代调用一次MoveNext(),执行一些类型检查等等。 最有可能影响性能测量的是MoveNext()的成本,因为它会被调用O(N)次。 也许它很便宜,但也许不是。
“for”关键字看起来更具可预测性,但在大多数“for”循环中,您会发现类似“collection [index]”的内容。 这看起来像一个简单的数组索引操作,但它实际上是一个方法调用,其成本完全取决于您正在迭代的集合的性质。 可能它便宜,但也许不是。
如果集合的底层结构本质上是一个链表,那么MoveNext便宜得多,但索引器可能有O(N)的成本,使得“for”循环的真实代价为O(N * N)。
这两个将运行几乎完全一样的方式。 写一些代码来使用两个,然后向他展示IL。 它应该显示可比较的计算,这意味着没有性能差异。
我发现foreach
循环更快地迭代List
。 看到我的测试结果如下。 在下面的代码中,我分别使用for
和foreach
循环迭代一个大小为100,10000和100000的array
来测量时间。
private static void MeasureTime() { var array = new int[10000]; var list = array.ToList(); Console.WriteLine("Array size: {0}", array.Length); Console.WriteLine("Array For loop ......"); var stopWatch = Stopwatch.StartNew(); for (int i = 0; i < array.Length; i++) { Thread.Sleep(1); } stopWatch.Stop(); Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch.ElapsedMilliseconds); Console.WriteLine(" "); Console.WriteLine("Array Foreach loop ......"); var stopWatch1 = Stopwatch.StartNew(); foreach (var item in array) { Thread.Sleep(1); } stopWatch1.Stop(); Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch1.ElapsedMilliseconds); Console.WriteLine(" "); Console.WriteLine("List For loop ......"); var stopWatch2 = Stopwatch.StartNew(); for (int i = 0; i < list.Count; i++) { Thread.Sleep(1); } stopWatch2.Stop(); Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch2.ElapsedMilliseconds); Console.WriteLine(" "); Console.WriteLine("List Foreach loop ......"); var stopWatch3 = Stopwatch.StartNew(); foreach (var item in list) { Thread.Sleep(1); } stopWatch3.Stop(); Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch3.ElapsedMilliseconds); }
更新
@jgauffin建议之后我用@johnskeet代码,发现for
循环的array
比下面的要快,
- Foreach循环与数组。
- 与列表循环。
- Foreach循环与列表。
看到我的测试结果和下面的代码,
private static void MeasureNewTime() { var data = new double[Size]; var rng = new Random(); for (int i = 0; i < data.Length; i++) { data[i] = rng.NextDouble(); } Console.WriteLine("Lenght of array: {0}", data.Length); Console.WriteLine("No. of iteration: {0}", Iterations); Console.WriteLine(" "); double correctSum = data.Sum(); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { double sum = 0; for (int j = 0; j < data.Length; j++) { sum += data[j]; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("For loop with Array: {0}", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (var i = 0; i < Iterations; i++) { double sum = 0; foreach (double d in data) { sum += d; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("Foreach loop with Array: {0}", sw.ElapsedMilliseconds); Console.WriteLine(" "); var dataList = data.ToList(); sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { double sum = 0; for (int j = 0; j < dataList.Count; j++) { sum += data[j]; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("For loop with List: {0}", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { double sum = 0; foreach (double d in dataList) { sum += d; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("Foreach loop with List: {0}", sw.ElapsedMilliseconds); }
因为具有更简单的逻辑来实现,所以它比foreach更快。
除非你在一个特定的速度优化过程,否则我会说使用哪个方法产生最简单的阅读和维护代码。
如果已经设置了一个迭代器,就像其中一个集合类一样,那么foreach就是一个很好的选择。 如果这是一个整数范围,你正在迭代,那么可能是更干净。
杰弗里·里希特(Jeffrey Richter)在最近的一个播客中谈到了for和foreach之间的性能差异: http : //pixel8.infragistics.com/shows/everything.aspx#Episode : 9317
在大多数情况下,没有什么区别。
Typically you always have to use foreach when you don't have an explicit numerical index, and you always have to use for when you don't actually have an iterable collection (eg iterating over a two-dimensional array grid in an upper triangle). There are some cases where you have a choice.
One could argue that for loops can be a little more difficult to maintain if magic numbers start to appear in the code. You should be right to be annoyed at not being able to use a for loop and have to build a collection or use a lambda to build a subcollection instead just because for loops have been banned.
Really screw with his head and go for an IQueryable .foreach closure instead:
myList.ForEach(c => Console.WriteLine(c.ToString());
大声笑
I wouldn't expect anyone to find a "huge" performance difference between the two.
I guess the answer depends on the whether the collection you are trying to access has a faster indexer access implementation or a faster IEnumerator access implementation. Since IEnumerator often uses the indexer and just holds a copy of the current index position, I would expect enumerator access to be at least as slow or slower than direct index access, but not by much.
Of course this answer doesn't account for any optimizations the compiler may implement.
Keep in mind that the for-loop and foreach-loop are not always equivalent. List enumerators will throw an exception if the list changes, but you won't always get that warning with a normal for loop. You might even get a different exception if the list changes at just the wrong time.
It seems a bit strange to totally forbid the use of something like a for loop.
There's an interesting article here that covers a lot of the performance differences between the two loops.
I would say personally I find foreach a bit more readable over for loops but you should use the best for the job at hand and not have to write extra long code to include a foreach loop if a for loop is more appropriate.
I did test it a while ago, with the result that a for
loop is much faster than a foreach
loop. The cause is simple, the foreach
loop first needs to instantiate an IEnumerator
for the collection.