for循环的限制是计算一次还是每次循环?

时间:2010-07-30 21:34:11

标签: c# loops

以下循环(12332 * 324234)中的限制是计算一次还是每次循环运行?

for(int i=0; i<12332*324234;i++)
{
    //Do something!
}

9 个答案:

答案 0 :(得分:29)

为此它计算一次,或者更可能是0次。

编译器会为你优化乘法。

然而,如果您有类似的话,情况并非总是如此。

for(int i=0; i<someFunction();i++)
{
    //Do something!
}

因为编译器并不总是能够看到someFunction将返回的内容。因此,即使someFunction每次都返回一个常量值,如果编译器不知道,也无法对其进行优化。

编辑:正如MainMa在评论中所说,在这种情况下,你可以通过这样做来消除成本:

int limit = someFunction();
for(int i=0; i<limit ;i++)
{
    //Do something!
}

IF 您确定someFunction()的值在循环过程中不会改变。

答案 1 :(得分:14)

这是C#中最常被误解的循环行为之一。

以下是您需要了解的内容:

  

循环边界计算,如果   非常数且涉及变量,   属性访问,函数调用或委托调用   将在每个之前重新计算边界的值   迭代循环。

所以,例如:

for( int i = 0; i < 1234*1234; i++ ) { ... }

在这种情况下,表达式1234*1234是编译时常量,因此在每次迭代时都不会重新计算。实际上,它是在编译时计算的,并用常量替换。

然而,在这种情况下:

int k = 10;
for( int i = 0; i < k; i++ ) { k -= 1; ... }

每次迭代都必须检查k的值。 毕竟它可以改变 ..在这个例子中呢。幸运的是,由于k只是一个局部变量,访问它的成本非常低 - 在许多情况下,它将保留在本地CPU缓存中,甚至可能保存在寄存器中(取决于JIT的方式)处理和发出机器代码。)

如果出现以下情况:

IEnumerable<int> sequence = ...;
for( int i = 0; i < sequence.Count(); i++ ) { ... }

计算sequence.Count()的成本可能非常昂贵。而且由于它在循环的每次迭代中进行了评估,因此可以快速累加。

编译器无法优化对循环边界表达式中出现的方法或属性的调用,因为它们也可能随着每次迭代而改变。想象一下,如果上面的循环写成:

IEnumerable<int> sequence = ...;
for( int i = 0; i < sequence.Count(); i++ ) {
    sequence = sequence.Concat( anotherItem );
}

显然sequence每次迭代都在改变...因此Count()在每次迭代时可能会有所不同。编译器不会尝试执行某些静态分析来确定循环边界表达式是否常量...即使不是不可能,也会非常复杂。相反,它假设如果表达式不是常量,则必须在每次迭代时对其进行求值。

现在,在大多数情况下,计算循环边界约束的成本相对便宜,因此您不必担心它。但是你需要了解编译器如何处理这样的循环边界。此外,作为开发人员,您需要注意使用具有副作用的属性或方法作为边界表达式的一部分 - 毕竟,这些副作用将在循环的每次迭代中发生。 / p>

答案 2 :(得分:7)

实际上它不会编译因为它会溢出但是如果你把它变成一个较小的数字并打开Reflector你会发现这样的东西。

for (int i = 0; i < 0x3cf7b0; i++)
{

}

答案 3 :(得分:3)

有两种方法可以解释您的问题:

  • 是否在每个循环上评估12332 * 324234的乘法
  • 是否在每个循环中评估循环条件中的表达式

这两个不同的问题的答案是:

  • 不,它实际上是在编译时评估的,因为它涉及两个常量
  • 是的,如有必要,他们是

换句话说:

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

如果对someString.Length的评估成本很高,则每次循环迭代都会受到惩罚。

答案 4 :(得分:2)

首先,问题中的for循环将无法编译。但是我们可以说它是

            for (int  i = 0; i < 20; i++)
        {
            Console.WriteLine(i);
            i++;

        }

VS

            for (int  i = 0; i < 10*2; i++)
        {
            Console.WriteLine(i);
            i++;

        }

IL代码完全相同。

   .method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: br.s L_0016
    L_0005: nop 
    L_0006: ldloc.0 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32)
    L_000c: nop 
    L_000d: ldloc.0 
    L_000e: ldc.i4.1 
    L_000f: add 
    L_0010: stloc.0 
    L_0011: nop 
    L_0012: ldloc.0 
    L_0013: ldc.i4.1 
    L_0014: add 
    L_0015: stloc.0 
    L_0016: ldloc.0 
    L_0017: ldc.i4.s 20
    L_0019: clt 
    L_001b: stloc.1 
    L_001c: ldloc.1 
    L_001d: brtrue.s L_0005
    L_001f: call int32 [mscorlib]System.Console::Read()
    L_0024: pop 
    L_0025: ret 
}

现在即使我用循环中的函数替换它,如

class Program
{
    static void Main(string[] args)
    {
        for (int  i = 0; i < Foo(); i++)
        {
            Console.WriteLine(i);
            i++;

        }
        Console.Read();
    }

    private static int Foo()
    {
        return 20;
    }

我得到这个IL代码

    .method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: br.s L_0016
    L_0005: nop 
    L_0006: ldloc.0 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32)
    L_000c: nop 
    L_000d: ldloc.0 
    L_000e: ldc.i4.1 
    L_000f: add 
    L_0010: stloc.0 
    L_0011: nop 
    L_0012: ldloc.0 
    L_0013: ldc.i4.1 
    L_0014: add 
    L_0015: stloc.0 
    L_0016: ldloc.0 
    L_0017: call int32 TestBedForums.Program::Foo()
    L_001c: clt 
    L_001e: stloc.1 
    L_001f: ldloc.1 
    L_0020: brtrue.s L_0005
    L_0022: call int32 [mscorlib]System.Console::Read()
    L_0027: pop 
    L_0028: ret 
}

看起来和我一样。

所以对我来说,看起来FOR FOR LOOP与有限限制没有区别,另一个有计算限制,最后是来自函数的限制。

所以只要代码复制,你知道你在这样一个庞大的循环中做了什么,并且你有足够的内存,我认为它会工作并产生相同的性能。 (在C#中)

答案 5 :(得分:0)

正如@Chaos所说,它不会编译。但是如果使用可表示的表达式(如100 * 100),结果可能会被硬编码。在Mono上,CIL包括:

IL_0007:  ldloc.0
IL_0008:  ldc.i4.1
IL_0009:  add
IL_000a:  stloc.0
IL_000b:  ldloc.0
IL_000c:  ldc.i4 10000
IL_0011:  blt IL_0007

正如您所看到的,100 * 100被硬编码为10000.但是,通常每次都会对其进行求值,如果调用方法或属性,则可能无法对其进行优化。

答案 6 :(得分:0)

看起来每次都要计算。从VS2008拆卸。

0000003b  nop              
            for (Int64 i = 0; i < (Int64)12332 * (Int64)324234; i++)
0000003c  mov         qword ptr [rsp+20h],0 
00000045  jmp         000000000000005E 
            {
00000047  nop              
                bool h = false;
00000048  mov         byte ptr [rsp+28h],0 
            }
0000004d  nop              
            for (Int64 i = 0; i < (Int64)12332 * (Int64)324234; i++)
0000004e  mov         rax,qword ptr [rsp+20h] 
00000053  add         rax,1 
00000059  mov         qword ptr [rsp+20h],rax 
0000005e  xor         ecx,ecx 
00000060  mov         eax,0EE538FB8h 
00000065  cmp         qword ptr [rsp+20h],rax 
0000006a  setl        cl   
0000006d  mov         dword ptr [rsp+2Ch],ecx 
00000071  movzx       eax,byte ptr [rsp+2Ch] 
00000076  mov         byte ptr [rsp+29h],al 
0000007a  movzx       eax,byte ptr [rsp+29h] 
0000007f  test        eax,eax 
00000081  jne         0000000000000047 

答案 7 :(得分:0)

除了KLee1的答案外,我在写绝对相同的东西,只是略有不同,以使其更易于理解:

for(int i=0, limit = someFunction(); i<limit ;i++)
{
    //Do something!
}

someFunction()仍然只执行一次,循环的头部仍然可以舒适地排成一行,而不会使您的队友感到困惑。希望! ;-)

顺便说一句,这也适用于C ++。

答案 8 :(得分:-2)

是的,每个循环周期计算比较值。

如果你绝对不得不使用for循环,这几乎不再是必需的,afaik只有一个“好的”循环模板:

for (int i = first(), last = last(); i != last; ++i)
{
// body
}

注意前缀增量。