Performance difference for control structures 'for' and 'foreach' in C#
哪个代码段可以提供更好的性能?以下代码段是用C编写的。
1。
1 2 3 4 | for(int counter=0; counter<list.Count; counter++) { list[counter].DoSomething(); } |
2。
1 2 3 4 | foreach(MyType current in list) { current.DoSomething(); } |
嗯,部分取决于
它是否有意义取决于你是否在循环中做了任何真正的工作。在几乎所有的情况下,性能的差异都不显著,但是可读性的差异有利于
我个人也会使用LINQ来避免"if"的出现:
1 2 3 | foreach (var item in list.Where(condition)) { } |
编辑:对于那些声称用
1 2 3 4 5 6 7 | static void IterateOverList(List<object> list) { foreach (object o in list) { Console.WriteLine(o); } } |
生产IL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | .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 |
编译器对数组的处理方式不同,将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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规范中记录的这个……
一个
1 2 3 4 5 6 7 8 9 | int tempCount = 0; while (tempCount < list.Count) { if (list[tempCount].value == value) { // Do something } tempCount++; } |
其中,作为一个
1 2 3 4 5 6 7 8 9 10 11 | using (IEnumerator<T> e = list.GetEnumerator()) { while (e.MoveNext()) { T o = (MyClass)e.Current; if (row.value == value) { // Do something } } } |
如您所见,这完全取决于枚举器是如何实现的,而列表索引器是如何实现的。事实证明,基于数组的类型的枚举器通常编写如下:
1 2 3 4 5 6 7 | private static IEnumerable<T> MyEnum(List<T> list) { for (int i = 0; i < list.Count; i++) { yield return list[i]; } } |
因此,正如您所看到的,在这种情况下,它不会有太大的区别,但是链表的枚举器可能会如下所示:
1 2 3 4 5 6 7 8 9 10 | 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
1 2 3 4 5 6 7 8 9 | public T this[int index] { LinkedListNode<T> current = this.First; for (int i = 1; i <= index; i++) { current = current.Next; } return current.value; } |
如您所见,在一个循环中多次调用这个函数要比使用一个能记住它在列表中的位置的枚举器慢得多。
一个简单的半验证测试。我做了一个小测试,只是为了看看。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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部分:
1 2 3 4 5 6 7 | foreach (int i in intList) { int foo = i * 2; if (foo % 2 == 0) { } } |
当我将for替换为foreach时——foreach比原来快了20毫秒——始终如一。结果是135-139ms,前臂113-119ms,我来回换了几次,确保不是某个过程刚刚开始。
但是,当我删除foo和if语句时,for的速度快了30毫秒(foreach为88毫秒,for为59毫秒)。他们都是空壳。我假设foreach实际上传递了一个变量,其中as-for只是递增一个变量。如果我增加
1 | int foo = intList[i]; |
然后for会变慢大约30毫秒。我假设这与它创建foo和获取数组中的变量并将其分配给foo有关。如果你只需要进入名单,那么你就不会受到惩罚。
说实话……我希望foreach在所有情况下都会稍微慢一点,但在大多数应用程序中都不重要。
编辑:下面是使用jons建议的新代码(134217728是抛出system.outofmemory异常之前可以得到的最大整数):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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
交换它们,看看它是否处理事物的顺序,会产生相同的结果(几乎)。
注意:对于C语言,这个答案比Java更适用于Java语言,因为C语言在EDOCX1 0中没有索引器,但我认为一般点仍然成立。
如果您所使用的
当使用索引器语法:
1 2 3 4 | list[0]; // head list[1]; // head.Next list[2]; // head.Next.Next // etc. |
当您调用
1 2 3 4 5 | 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. |
如您所见,在
当然,正如乔恩所说,这确实取决于
像其他人提到的那样,尽管性能实际上并不重要,但是由于循环中使用了
1 2 3 4 5 | IEnumerator iterator = ((IEnumerable)list).GetEnumerator(); while (iterator.MoveNext()) { var item = iterator.Current; // do stuff } |
这是C中构造的等效展开。您可以想象性能影响如何根据moveNext和current的实现而变化。而在数组访问中,您没有这种依赖关系。
在阅读了足够多的"foreach循环应该是可读性的首选"论点之后,我可以说我的第一个反应是"什么"?可读性,一般来说,是主观的,在这个特定的例子中,甚至更多。对于有编程背景的人(实际上,Java之前的每种语言),for循环比Frach循环要容易得多。此外,声称foreach循环更具可读性的人,也是linq和其他"特性"的支持者,这些特性使代码难以读取和维护,这证明了上述观点。
关于对性能的影响,请参阅此问题的答案。
编辑:C(如哈希集)中有没有索引器的集合。在这些集合中,foreach是唯一迭代的方法,我认为它是唯一应该使用它的情况。
您可以在deep.net中阅读它-第1部分迭代
它涵盖了从.NET源代码一直到反汇编的结果(没有第一次初始化)。
例如-使用foreach循环的数组迭代:
和-列出foreach循环的迭代:
最终结果是:
在您提供的示例中,最好使用
标准的
因此,对于日常代码,性能不是使用更复杂的
查看此链接:http://www.codeproject.com/articles/146797/fast-and-less-fast-loops-in-c
1 2 3 4 5 6 7 8 9 10 | ╔══════════════════════╦═══════════╦═══════╦════════════════════════╦═════════════════════╗ ║ 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 ║ ╚══════════════════════╩═══════════╩═══════╩════════════════════════╩═════════════════════╝ |
还有一个有趣的事实,当测试两个循环的速度时,很容易忽略:使用调试模式不会让编译器使用默认设置优化代码。
这使我得到一个有趣的结果,在调试模式下foreach比for更快。而在释放模式下,for ist比foreach更快。显然,编译器有更好的方法来优化for循环,而不是foreach循环,后者会破坏几个方法调用。for循环是如此的基本,以至于它甚至可能由CPU本身优化。