65.9K
CodeProject 正在变化。 阅读更多。
Home

FOREACH 与 FOR (C#)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (81投票s)

2004年4月20日

4分钟阅读

viewsIcon

658393

downloadIcon

2

深入检查 foreach 和 for 循环之间的性能差异。

引言

根据我的经验,程序员有两种。一种是写代码完成工作的人,另一种是想写好代码的人。但是,这里有一个大问题。什么是好代码?好代码源于良好的编程实践。什么是良好的编程实践?实际上,我在这里的目的不是讨论良好的编程实践(我计划将来写一些相关的文章!),而是更多地谈论如何写出更有效的代码。我将深入探讨两种目前常用的循环,以及它们在性能方面的差异。

背景

必须熟悉 IL 和汇编代码。此外,最好对 .NET 框架的工作原理有深入的了解。还需要对 JIT 有一些了解才能理解究竟发生了什么。

使用代码

我将使用一段非常小的代码来演示两个流行的循环语句:forforeach。我们将看一些代码,并更详细地了解其功能。

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

© . All rights reserved.