在C#中控制结构'for'和'foreach'的性能差异

哪个代码片段可以提供更好的性能? 下面的代码段是用C#编写的。

1。

for(int counter=0; counter<list.Count; counter++) { list[counter].DoSomething(); } 

2。

 foreach(MyType current in list) { current.DoSomething(); } 

那么,这部分取决于list的确切types。 它也将取决于你正在使用的确切的CLR。

无论是否有意义 ,都取决于你是否在循环中做了真正的工作。 在几乎所有情况下,性能的差异都不会很大,但是可读性的差异有利于foreach循环。

我个人使用LINQ来避免“if”:

 foreach (var item in list.Where(condition)) { } 

编辑:对于那些声称用foreach迭代List<T>for循环产生相同的代码的人for ,下面的证据表明它没有:

 static void IterateOverList(List<object> list) { foreach (object o in list) { Console.WriteLine(o); } } 

产生IL:

 .method private hidebysig static void IterateOverList(class [mscorlib]System.Collections.Generic.List`1<object> list) cil managed { // Code size 49 (0x31) .maxstack 1 .locals init (object V_0, valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> V_1) IL_0000: ldarg.0 IL_0001: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<object>::GetEnumerator() IL_0006: stloc.1 .try { IL_0007: br.s IL_0017 IL_0009: ldloca.s V_1 IL_000b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::get_Current() IL_0010: stloc.0 IL_0011: ldloc.0 IL_0012: call void [mscorlib]System.Console::WriteLine(object) IL_0017: ldloca.s V_1 IL_0019: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::MoveNext() IL_001e: brtrue.s IL_0009 IL_0020: leave.s IL_0030 } // end .try finally { IL_0022: ldloca.s V_1 IL_0024: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_002f: endfinally } // end handler IL_0030: ret } // end of method Test::IterateOverList 

编译器以不同的方式处理数组 ,将foreach循环基本上转换为for循环,而不是List<T> 。 以下是数组的等效代码:

 static void IterateOverArray(object[] array) { foreach (object o in array) { Console.WriteLine(o); } } // Compiles into... .method private hidebysig static void IterateOverArray(object[] 'array') cil managed { // Code size 27 (0x1b) .maxstack 2 .locals init (object V_0, object[] V_1, int32 V_2) IL_0000: ldarg.0 IL_0001: stloc.1 IL_0002: ldc.i4.0 IL_0003: stloc.2 IL_0004: br.s IL_0014 IL_0006: ldloc.1 IL_0007: ldloc.2 IL_0008: ldelem.ref IL_0009: stloc.0 IL_000a: ldloc.0 IL_000b: call void [mscorlib]System.Console::WriteLine(object) IL_0010: ldloc.2 IL_0011: ldc.i4.1 IL_0012: add IL_0013: stloc.2 IL_0014: ldloc.2 IL_0015: ldloc.1 IL_0016: ldlen IL_0017: conv.i4 IL_0018: blt.s IL_0006 IL_001a: ret } // end of method Test::IterateOverArray 

有趣的是,我找不到任何地方的C#3规范中logging的这个…

一个for循环被编译成代码大致相当于这个:

 int tempCount = 0; while (tempCount < list.Count) { if (list[tempCount].value == value) { // Do something } tempCount++; } 

作为一个foreach循环被编译成代码大约相当于这个:

 using (IEnumerator<T> e = list.GetEnumerator()) { while (e.MoveNext()) { T o = (MyClass)e.Current; if (row.value == value) { // Do something } } } 

正如你所看到的,这将取决于枚举器是如何实现的,而列表索引器是如何实现的。 事实certificate,基于数组的types的枚举器通常是这样写的:

 private static IEnumerable<T> MyEnum(List<T> list) { for (int i = 0; i < list.Count; i++) { yield return list[i]; } } 

正如你所看到的,在这个例子中,它不会有太大的区别,但是链表的枚举器可能看起来像这样:

 private static IEnumerable<T> MyEnum(LinkedList<T> list) { LinkedListNode<T> current = list.First; do { yield return current.Value; current = current.Next; } while (current != null); } 

在.NET中,你会发现LinkedList <T>类甚至没有索引器,所以你不能在链表上做for循环。 但如果可以的话,索引器就必须这样写:

 public T this[int index] { LinkedListNode<T> current = this.First; for (int i = 1; i <= index; i++) { current = current.Next; } return current.value; } 

正如你所看到的,在一个循环中多次调用会比使用枚举器慢得多,它可以记住它在列表中的位置。

一个简单的testing半validation。 我做了一个小testing,只是为了看。 这里是代码:

 static void Main(string[] args) { List<int> intList = new List<int>(); for (int i = 0; i < 10000000; i++) { intList.Add(i); } DateTime timeStarted = DateTime.Now; for (int i = 0; i < intList.Count; i++) { int foo = intList[i] * 2; if (foo % 2 == 0) { } } TimeSpan finished = DateTime.Now - timeStarted; Console.WriteLine(finished.TotalMilliseconds.ToString()); Console.Read(); } 

这里是foreach部分:

 foreach (int i in intList) { int foo = i * 2; if (foo % 2 == 0) { } } 

当我用foreachreplace这个“foreach”时,这个foreach一直是20毫秒。 目前是135-139ms,而foreach是113-119ms。 我多次来回交换,确保这不是一个刚进入的过程。

但是,当我删除foo和if语句时,for是更快了30毫秒(foreach是88ms和59ms)。 他们都是空的炮弹。 我假设foreach实际上传递了一个variables,因为for只是递增一个variables。 如果我加了

 int foo = intList[i]; 

然后变慢30ms左右。 我假设这与创buildfoo和抓取数组中的variables并将其分配给foo有关。 如果你只是访问intList [i],那么你没有这个惩罚。

诚实地说,我希望在任何情况下,foreach都会稍微慢一些,但是在大多数应用程序中,这些不够重要。

编辑:这里是使用Jonsbuild议的新代码(134217728是在引发System.OutOfMemoryexception之前最大的int):

 static void Main(string[] args) { List<int> intList = new List<int>(); Console.WriteLine("Generating data."); for (int i = 0; i < 134217728 ; i++) { intList.Add(i); } Console.Write("Calculating for loop:\t\t"); Stopwatch time = new Stopwatch(); time.Start(); for (int i = 0; i < intList.Count; i++) { int foo = intList[i] * 2; if (foo % 2 == 0) { } } time.Stop(); Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms"); Console.Write("Calculating foreach loop:\t"); time.Reset(); time.Start(); foreach (int i in intList) { int foo = i * 2; if (foo % 2 == 0) { } } time.Stop(); Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms"); Console.Read(); } 

结果如下:

生成数据。 计算循环:2458ms计算foreach循环:2005ms

交换他们周围,看看它处理的事情的顺序产生相同的结果(几乎)。

注意:这个答案更适用于Java而不是C#,因为C#在LinkedLists上没有索引器,但是我认为一般的观点仍然存在。

如果你正在使用的list碰巧是一个LinkedList ,那么索引器代码( 数组式的访问)的性能比使用来自foreachIEnumerator要糟糕得多。

当您使用索引器语法: list[10000]访问LinkedList中的元素10.000时,链接列表将从头节点开始,并遍历Next pointers万次,直到到达正确的对象。 显然,如果你循环做这个,你会得到:

 list[0]; // head list[1]; // head.Next list[2]; // head.Next.Next // etc. 

当你调用GetEnumerator (隐式使用forach -syntax)时,你会得到一个IEnumerator对象,它有一个指向头节点的指针。 每次调用MoveNext ,该指针都将移动到下一个节点,如下所示:

 IEnumerator em = list.GetEnumerator(); // Current points at head em.MoveNext(); // Update Current to .Next em.MoveNext(); // Update Current to .Next em.MoveNext(); // Update Current to .Next // etc. 

正如你所看到的,在LinkedList的情况下,数组索引器方法变得越来越慢,循环越久(它必须一遍又一遍地通过相同的头指针)。 而IEnumerable只是在一段时间内运行。

当然,正如Jon所说,这真的取决于list的types,如果list不是LinkedList ,而是一个数组,那么行为就完全不同了。

就像其他人提到的,虽然性能实际上并不重要,但由于循环中IEnumerable / IEnumerator使用,foreach总是会慢一些。 编译器将该构造转换为该接口上的调用,并且在foreach构造中为每个步骤调用一个函数+一个属性。

 IEnumerator iterator = ((IEnumerable)list).GetEnumerator(); while (iterator.MoveNext()) { var item = iterator.Current; // do stuff } 

这是C#中构造的等效扩展。 您可以想象,根据MoveNext和Current的实现,性能影响可能会有所不同。 而在数组访问中,您没有这种依赖关系。

在阅读了足够的论据以后,我认为我的第一反应是“什么”? 一般而言,可读性是主观的,在这个特殊情况下,更是如此。 对于具有编程背景(实际上,Java之前的每种语言)的人来说,for循环比foreach循环更容易阅读。 另外,同样的人声称foreach循环更具可读性,也是linq和其他“特性”的支持者,使得代码难以阅读和维护,这些都certificate了上述观点。

关于对性能的影响,请参阅此问题的答案。

编辑:在C#中的集合(如HashSet)没有索引器。 在这些集合中, foreach是迭代的唯一方法,并且这是唯一的情况,我认为应该使用它。

还有一个有趣的事实是,在testing两个循环的速度时可能很容易被忽略:使用debugging模式不会让编译器使用默认设置来优化代码。

这导致了一个有趣的结果,即foreach比在debugging模式下更快。 而在释放模式下,它比foreach更快。 很明显,编译器有更好的方法来优化一个for循环比foreach循环妥协几个方法调用。 一个for循环就是这样的基础,它甚至可能被CPU本身优化。

在你提供的例子中,使用foreach循环代替for循环肯定更好。

除非循环已经展开(每个步骤1.0个循环),否则标准的foreach构造可以比简单的for-loop (每步2个循环)更快(每步1.5个循环)。

所以对于日常代码,性能不是使用更复杂for whiledo-while结构的理由。

看看这个链接: http : //www.codeproject.com/Articles/146797/Fast-and-Less-Fast-Loops-in-C


 ╔══════════════════════╦═══════════╦═══════╦════════════════════════╦═════════════════════╗ ║ Method ║ List<int> ║ int[] ║ Ilist<int> onList<Int> ║ Ilist<int> on int[] ║ ╠══════════════════════╬═══════════╬═══════╬════════════════════════╬═════════════════════╣ ║ Time (ms) ║ 23,80 ║ 17,56 ║ 92,33 ║ 86,90 ║ ║ Transfer rate (GB/s) ║ 2,82 ║ 3,82 ║ 0,73 ║ 0,77 ║ ║ % Max ║ 25,2% ║ 34,1% ║ 6,5% ║ 6,9% ║ ║ Cycles / read ║ 3,97 ║ 2,93 ║ 15,41 ║ 14,50 ║ ║ Reads / iteration ║ 16 ║ 16 ║ 16 ║ 16 ║ ║ Cycles / iteration ║ 63,5 ║ 46,9 ║ 246,5 ║ 232,0 ║ ╚══════════════════════╩═══════════╩═══════╩════════════════════════╩═════════════════════╝