FOREACH 与 FOR (C#)






3.50/5 (81投票s)
2004年4月20日
4分钟阅读

658393

2
深入检查 foreach 和 for 循环之间的性能差异。
引言
根据我的经验,程序员有两种。一种是写代码完成工作的人,另一种是想写好代码的人。但是,这里有一个大问题。什么是好代码?好代码源于良好的编程实践。什么是良好的编程实践?实际上,我在这里的目的不是讨论良好的编程实践(我计划将来写一些相关的文章!),而是更多地谈论如何写出更有效的代码。我将深入探讨两种目前常用的循环,以及它们在性能方面的差异。
背景
必须熟悉 IL 和汇编代码。此外,最好对 .NET 框架的工作原理有深入的了解。还需要对 JIT 有一些了解才能理解究竟发生了什么。
使用代码
我将使用一段非常小的代码来演示两个流行的循环语句:for
和 foreach
。我们将看一些代码,并更详细地了解其功能。
FOR
int[] myInterger = new int[1];
int total = 0;
for(int i = 0; i < myInterger.Length; i++)
{
total += myInterger[i];
}
foreach
int[] myInterger = new int[1];
int total = 0;
foreach(int i in myInterger)
{
total += i;
}
两种代码都会产生相同的结果。foreach
用于遍历集合,而 for
可以用于任何目的。我将不解释代码本身。在深入之前,我认为大家应该都熟悉用于生成 IL 代码的 ILDasm,以及通常用于生成 JIT 编译代码的 CorDbg 工具。
C# 编译器生成的 IL 代码在一定程度上是经过优化的,剩余部分留给 JIT 处理。总之,这并不是真正对我们重要的。所以,当我们谈论优化时,我们必须考虑两件事。第一是 C# 编译器,第二是 JIT。
因此,与其深入研究 IL 代码,不如看看 JIT 发出的代码。这是将在我们的机器上运行的代码。我现在使用的是 AMD Athlon 1900+。代码高度依赖于我们的硬件。因此,您从您的机器上得到的结果可能与我的有所不同。不过,算法的变化不会太大。
在变量声明方面,foreach
有五个变量声明(三个 Int32
整数和两个 Int32
数组),而 for
只有三个(两个 Int32
整数和一个 Int32
数组)。在循环遍历时,foreach
会将当前数组复制到一个新数组中进行操作。而 for
则不关心这部分。
在这里,我将深入探讨代码之间的确切差异。
FOR
Instruction Effect
cmp dword ptr [eax+4],0 i<;myInterger.Length
jle 0000000F
mov ecx,dword ptr [eax+edx*4+8] total += myInterger[i]
inc edx ++i
cmp esi,dword ptr [eax+4] i<;myInterger.Length
jl FFFFFFF8
我将解释这里发生的事情。保存 i
值和 myInteger
数组长度的 esi
寄存器在两个阶段进行比较。第一个阶段只执行一次以检查条件,如果循环可以继续,则添加值。对于循环,它在第二阶段完成。循环内部经过了很好的优化,如前所述,工作以完美的优化方式完成。
foreach
Instruction Effect
cmp esi,dword ptr [ebx+4] i<;myInterger.Length
jl FFFFFFE3
cmp esi,dword ptr [ebx+4] i<;myInterger.Length
jb 00000009
mov eax,dword ptr [ebx+esi*4+8]
mov dword ptr [ebp-0Ch],eax
mov eax,dword ptr [ebp-0Ch]
add dword ptr [ebp-8],eax total += i
inc esi ++i
cmp esi,dword ptr [ebx+4] i<;myInterger.Length
jl FFFFFFE3
任何人都会说它们不一样。但我们将看看它为什么与 for
循环不同。差异的主要原因是编译器对它们的理解不同。它们使用的算法不同。连续的两个比较语句是不必要的。它毫无理由地一遍又一遍地做同样的事情!
cmp esi,dword ptr [ebx+4]
jl FFFFFFE3
cmp esi,dword ptr [ebx+4]
它还使用了一些不必要的移动语句,这些语句也可能(不总是,取决于情况)降低代码的性能。foreach
将所有内容都视为集合并将其作为集合处理。我认为,这也会降低工作效率。
因此,我强烈认为,如果您计划编写高性能代码而不是针对集合,请使用 for
循环。即使是对于集合,foreach
在使用时可能看起来很方便,但它并非那么高效。因此,我强烈建议每个人在任何时候都使用 for
循环而不是 foreach
。
关注点
实际上,我对主要在 .NET 语言中的代码性能问题进行了一些小研究。在我测试时,我发现了解 JIT 的工作原理以及调试 JIT 编译器生成的代码确实是必须的。理解代码花了一些时间。
历史
本文提交于 2004 年 4 月 19 日。