在。net中,哪个循环运行得更快,'for'或& # 39;foreach # 39; ?

在c# / VB.NET/。forforeach,哪个循环运行得更快?

自从我读到for循环比foreach循环和很久以前工作得快,我就认为它适用于所有集合、泛型集合、所有数组等。

我搜索了谷歌,找到了几篇文章,但大多数都是不确定的(阅读文章评论),而且是开放式的。

理想的情况是列出每种情况以及最佳解决方案。

例如(这只是一个例子):

  1. 用于迭代1000+的数组 - for优于foreach
  2. 用于迭代IList(非泛型)字符串- foreach更好 李比for < / >

在网上找到了一些相同的参考资料:

  1. 由Emmanuel Schanzer原创的伟大旧文章
  2. CodeProject FOREACH Vs. FOR
  3. 博客- foreach或不foreach,这是问题
  4. ASP。NET论坛- NET 1.1 c# for vs foreach

(编辑)

除了可读性之外,我对事实和数据真的很感兴趣。在某些应用中,最后一英里的性能优化确实很重要。

281224 次浏览

我的猜测是,在99%的情况下,它可能并不重要,所以为什么要选择更快的,而不是最合适的(最容易理解/维护)?

它将永远接近。对于数组,有时 for稍微快一点,但foreach更有表现力,并提供LINQ等。一般来说,坚持使用foreach

此外,foreach可能在某些情况下被优化。例如,一个链表对索引器来说可能很糟糕,但对foreach来说可能很快。实际上,由于这个原因,标准LinkedList<T>甚至没有提供索引器。

我在一段时间前测试了它,结果是for循环比foreach循环快得多。原因很简单,foreach循环首先需要为集合实例化一个IEnumerator

For有更简单的逻辑实现,所以它比foreach快。

Patrick Smacchia 在博客上写过上个月,得出以下结论:

  • for循环在List上比foreach便宜2倍多一点
  • 在array上循环比在List上循环便宜2倍左右。
  • 因此,使用for对数组进行循环要便宜5倍 而不是使用foreach在List上循环 (我相信,这是我们都在做的)

当你遍历数组、列表等常见结构时,for-和__abc1 -循环的速度差异很小,并且在集合上执行LINQ查询几乎总是稍微慢一些,尽管它写起来更好!正如其他海报上说的那样,追求表现力而不是多出一毫秒的性能。

到目前为止还没有说的是,当编译foreach循环时,编译器会根据它迭代的集合对它进行优化。这意味着当你不确定使用哪个循环时,你应该使用foreach循环——它会在编译时为你生成最好的循环。它的可读性也更强。

foreach循环的另一个关键优势是,如果你的集合实现发生了变化(例如,从int类型array变为List<int>),那么你的foreach循环将不需要任何代码更改:

foreach (int i in myCollection)

不管你的集合是什么类型,上面的内容都是一样的,而在你的for循环中,如果你将myCollectionarray更改为List,则不会构建以下内容:

for (int i = 0; i < myCollection.Length, i++)

这可能取决于您枚举的集合类型及其索引器的实现。一般来说,使用foreach可能是一个更好的方法。

而且,它可以与任何IEnumerable一起工作——而不仅仅是与索引器一起工作。

两者之间不太可能有巨大的性能差异。与往常一样,当面对“哪个更快?”的问题时,您应该始终认为“我可以测量这个”。

在循环体中编写两个做相同事情的循环,执行并计时,并查看速度的差异。使用一个几乎为空的主体和一个与您实际要做的类似的循环主体来执行此操作。还可以尝试使用您正在使用的集合类型,因为不同类型的集合可能具有不同的性能特征。

首先,对德米特里(现已删除)的回答提出反诉。对于数组,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循环,因为它使代码更简单——因为可读性几乎总是重要的,而微优化很少是。

我建议阅读以获得具体的答案。本文的结论是,使用for循环通常比foreach循环更好更快。

除非您处于特定的速度优化过程中,否则我会建议使用产生最容易阅读和维护代码的方法。

如果已经设置了迭代器,比如其中一个集合类,那么foreach是一个很好的简单选项。如果你迭代的是一个整数范围,那么for可能更简洁。

Jeffrey Richter在最近的播客中谈到了for和foreach之间的性能差异:http://pixel8.infragistics.com/shows/everything.aspx#Episode:9317

我不期望任何人发现两者之间的“巨大”性能差异。

我想答案取决于您试图访问的集合是否具有更快的indexer访问实现或更快的IEnumerator访问实现。由于IEnumerator经常使用索引器,并且仅保存当前索引位置的副本,因此我希望枚举器访问至少与直接索引访问一样慢或更慢,但不会慢太多。

当然,这个答案没有解释编译器可能实现的任何优化。

请记住,for循环和foreach-循环并不总是等价的。如果列表更改,列表枚举器将抛出异常,但使用普通的for循环并不总是会得到该警告。如果列表在错误的时间更改,甚至可能会得到不同的异常。

Jeffrey Richter在techhed 2005上说:

“这些年来,我逐渐认识到c#编译器对我来说基本上是一个骗子。”.. “它在很多事情上都说谎。”.. “就像你做foreach循环……”.. "...这是你写的一小行代码,但c#编译器为了做到这一点而吐出的东西是惊人的。它在那里放置了一个try/finally块,在finally块中,它将变量强制转换为一个IDisposable接口,如果强制转换成功,它将调用Dispose方法,在循环中,它将调用Current属性和MoveNext方法,在循环中,对象正在被创建。很多人使用foreach,因为它很容易编码,很容易操作。”.. “foreach在性能方面不是很好,如果你在一个集合上迭代,而不是使用方括号符号,只是做索引,那要快得多,而且它不会在堆上创建任何对象……”

< p >按需网络直播: http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032292286&EventCategory=3&culture=en-US&CountryCode=US < / p >

__ABC0循环比for循环展示了更具体的意图

使用foreach循环向任何使用你的代码的人表明,你计划对集合中的每个成员做一些事情,而不管它在集合中的位置。它还显示您没有修改原始集合(如果您试图修改,则会抛出异常)。

foreach的另一个优点是它适用于任何IEnumerable,其中for只适用于IList,其中每个元素实际上都有一个索引。

但是,如果需要使用元素的索引,那么当然应该允许使用for循环。但是如果您不需要使用索引,那么使用索引只会使您的代码变得混乱。

据我所知,这对性能没有重大影响。在未来的某个阶段,使用foreach调整代码以在多核上运行可能会更容易,但这不是现在需要担心的事情。

在处理对象集合的情况下,foreach更好,但如果你增加一个数字,for循环更好。

注意,在最后一种情况下,你可以这样做:

foreach (int i in Enumerable.Range(1, 10))...

但它肯定不会表现得更好,它实际上比for的性能更差。

两者的运行方式几乎完全相同。编写一些代码来使用两者,然后向他展示IL。它应该显示类似的计算,这意味着在性能上没有差异。

每当有关于性能的争论时,您只需要编写一个小测试,以便您可以使用量化结果来支持您的案例。

使用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

幸运的是,这样做的结果表明差异可以忽略不计,您还可以在最可维护的代码中执行任何结果

至少我没有看到我的同事或更高的人这么说,考虑到forforeach之间没有显著的速度差异,这是荒谬的。如果他要求在所有情况下都使用它,同样适用!

完全禁止使用for循环之类的东西似乎有点奇怪。

有一篇有趣的文章在这里涵盖了两个循环之间的很多性能差异。

我个人会说,我发现foreach比for循环更具可读性,但你应该使用最好的方法来完成手头的工作,如果for循环更合适的话,就不必编写额外的长代码来包含foreach循环。

在大多数情况下,其实没有什么区别。

通常,当你没有显式的数值索引时,你总是必须使用foreach,而当你实际上没有可迭代集合时,你总是必须使用for(例如,在上三角形的二维数组网格上迭代)。在某些情况下,你可以做出选择。

有人可能会说,如果代码中开始出现神奇的数字,for循环可能会更难维护。你对不能使用for循环而不得不构建一个集合或使用lambda来构建子集合感到恼火应该是正确的,因为for循环已经被禁止了。

有很好的理由使用更喜欢 foreach循环而不是for循环。如果你可以使用foreach循环,你的老板说你应该这样做是对的。

然而,并不是每次迭代都是简单地按顺序逐个遍历列表。如果他是禁止 for,是的,这是错误的。

如果我是你,我会做的是把你所有自然的for循环变成递归。这样可以教会他,对你来说也是很好的脑力锻炼。

for是否比foreach快并不重要。我非常怀疑选择其中之一会对你的表现产生重大影响。

优化应用程序的最佳方法是对实际代码进行分析。这将精确地找出占用最多工作/时间的方法。首先优化它们。如果性能仍然不能接受,请重复上述步骤。

一般来说,我建议不要进行微观优化,因为它们很少会产生任何显著的收益。唯一的例外是在优化确定的热门路径时(即,如果您的分析确定了一些高度使用的方法,那么广泛地优化这些方法可能是有意义的)。

你可以用一个IQueryable .foreach闭包来代替:

myList.ForEach(c => Console.WriteLine(c.ToString());

“有没有什么论据可以帮助我说服他使用for循环是可以接受的?”

不,如果你的老板事无巨细地告诉你应该使用哪种编程语言结构,你就真的没什么可说的了。对不起。

我认为在大多数情况下,for比foreach稍微快一点,但这真的没有抓住重点。有一件事我没有看到提到的是,在你正在谈论的场景中(即,一个大容量的web应用程序),for和foreach之间的性能差异对站点的性能没有影响。你将受到请求/响应时间和DB时间的限制,而不是v. foreach。

也就是说,我不理解你对foreach的厌恶。在我看来,foreach在任何一个都可以使用的情况下通常更清楚。我通常为需要以某种丑陋的、非标准的方式遍历集合的情况而保留。

它是你所做的内部循环影响性能,而不是实际的循环构造(假设你的情况是非平凡的)。

每种语言结构都有适当的使用时间和地点。c#语言有四个独立的迭代语句是有原因的——每个迭代语句都有特定的目的,并且有适当的用法。

我建议你和你的老板坐下来,试着理性地解释为什么for循环有一个目的。有时候,for迭代块比foreach迭代块更清楚地描述算法。在这种情况下,使用它们是合适的。

我还要向你的老板指出——性能不是,也不应该是任何实际方式的问题——这更像是用简洁、有意义、可维护的方式表达算法的问题。这样的微优化完全忽略了性能优化的要点,因为任何真正的性能好处都来自算法重新设计和重构,而不是循环重构。

如果在理性的讨论之后,仍然有这种权威主义的观点,那就取决于你如何继续下去了。就我个人而言,我不会喜欢在一个不鼓励理性思考的环境中工作,我会考虑跳槽到另一个雇主手下。然而,我强烈建议在感到不安之前讨论一下——这可能只是一个简单的误解。

这可以节省你:

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);
}

为了获得更大的胜利,你可以把三名代表作为参数。

在我的Windows Mobile项目中,我使用for循环作为控件集合。20个控制需要100毫秒!foreach循环只使用了4 ms。这是一个性能问题……

我找到了foreach循环,它遍历一个List 。下面是我的测试结果。在下面的代码中,我分别迭代大小为100、10000和100000的array,使用forforeach循环来测量时间。

enter image description here

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循环与数组。
  • For带列表的循环。
  • Foreach循环与列表。

请看下面我的测试结果和代码,

enter image description here

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);
}

我需要使用三个嵌套循环(在List<MyCustomType>上)对一些大数据进行解析。我想,使用Rob Fonseca-Ensor上面的帖子,计算并比较使用foreach的差异会很有趣。

差异是: foreach(三个foreach嵌套,如foreach{foreach{forech{}}})在171.441秒内完成了这项工作,而as for (for{for{for{}}})在158.616秒内完成了这项工作

现在13秒大约是13%的时间减少,这对我来说有点重要。然而,foreach绝对比使用三个索引for…

这和大多数“哪个更快”的问题有相同的两个答案:

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)。

《编写高性能。net代码》一书的作者Ben Watson:

"这些优化对你的程序重要吗?只有当你的程序 CPU的限制和收集迭代是你的核心部分 处理。如你所见,有很多方法可以伤害你的 如果你不小心,就会失去效果,但只有当它是 这是你计划的重要组成部分。我的哲学是: 大多数人不需要知道这些,但如果你知道,那就理解了 系统的每一层都很重要,这样你才能 聪明的抉择”。< / p >

最严厉的解释可以在这里找到:http://www.codeproject.com/Articles/844781/Digging-Into-NET-Loop-Performance-Bounds-checking

我提到这个细节是基于for和foreach的收集速度。

List -For循环比Foreach循环略快

ArrayList - For循环的速度比Foreach循环快2倍多。

数组-两者的速度相同。但是Foreach Loop似乎更快一些。

你可以在深度。net -第1部分迭代中读到它

它覆盖了从。net源代码一直到反汇编的结果(没有第一次初始化)。

for example -数组迭代与foreach循环: enter image description here < / p >

和- list迭代与foreach循环: enter image description here < / p >

和最终结果: enter image description here < / p >

enter image description here

    internal static void Test()
{
int LOOP_LENGTH = 10000000;
Random random = new Random((int)DateTime.Now.ToFileTime());


{
Dictionary<int, int> dict = new Dictionary<int, int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i, i);
}


for (int i = 0; i < LOOP_LENGTH; i++)
{
for (int k = 0; k < dict.Count; k++)
{
if (dict[k] > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"Dictionary for T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");


GC.Collect();
}




{
Dictionary<int, int> dict = new Dictionary<int, int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i, i);
}


for (int i = 0; i < LOOP_LENGTH; i++)
{
foreach (var item in dict)
{
if (item.Value > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"Dictionary foreach T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");


GC.Collect();
}


{
Dictionary<int, int> dict = new Dictionary<int, int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i, i);
}


for (int i = 0; i < LOOP_LENGTH; i++)
{
foreach (var item in dict.Values)
{
if (item > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"Dictionary foreach values T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");


GC.Collect();
}




{
List<int> dict = new List<int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i);
}


for (int i = 0; i < LOOP_LENGTH; i++)
{
for (int k = 0; k < dict.Count; k++)
{
if (dict[k] > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"list for T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");


GC.Collect();
}




{
List<int> dict = new List<int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i);
}


for (int i = 0; i < LOOP_LENGTH; i++)
{
foreach (var item in dict)
{
if (item > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"list foreach T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");


GC.Collect();
}
}

字典为T:10.1957728s M:2080
字典T:10.5900586 M:1952
字典值T:3.8294776s M:2088
T:3.7981471s M:320
list foreach T:4.4861377s M:648

一个强大而精确的测量时间的方法是使用BenchmarkDotNet库。

在下面的示例中,我在for/foreach上对1,000,000,000个整数记录进行了循环,并用BenchmarkDotNet进行测量:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;


public class Program
{
public static void Main()
{
BenchmarkRunner.Run<LoopsBenchmarks>();
}
}


[MemoryDiagnoser]
public class LoopsBenchmarks
{
private List<int> arr = Enumerable.Range(1, 1_000_000_000).ToList();


[Benchmark]
public void For()
{
for (int i = 0; i < arr.Count; i++)
{
int item = arr[i];
}
}


[Benchmark]
public void Foreach()
{
foreach (int item in arr)
{
}
}
}

结果如下:

enter image description here

结论

在上面的例子中,我们可以看到,对于列表,for循环比foreach循环略快。我们还可以看到两者使用相同的内存分配。