2010-07-30 9 views
17

¿Se calcula el límite en el siguiente ciclo (12332 * 324234) una vez o cada vez que se ejecuta el ciclo?¿Los límites de los bucles for se calculan una vez o con cada bucle?

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

posible duplicado de [C# - For-loop internals] (http://stackoverflow.com/questions/3369773/c-for-loop-internals) –

+0

@Darin, no realmente. Eso usó un método/propiedad, no una constante. –

+1

Mal ejemplo; el compilador optimizará cálculos estáticos como esos donde los encuentre. –

Respuesta

27

Para esto se calculó una vez, o más probable 0 veces.

El compilador optimizará la multiplicación para usted.

Sin embargo, este no es siempre el caso si tiene algo así como.

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

Debido a que el compilador no siempre es capaz de ver lo que someFunction volverá. Entonces, incluso si someFunction devuelve un valor constante cada vez, si el compilador no lo sabe, no puede optimizarlo.

EDITAR: Como dijo MainMa en un comentario, que se encuentran en esta situación, puede eliminar el costo de hacer algo como esto:

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

SI está seguro de que el valor de someFunction() se no cambiar durante el ciclo

+1

Es cierto. Por ejemplo, hacer una consulta a una base de datos en 'someFunction()' será realmente una mala idea. Puede evitar esto fácilmente creando un número entero antes del ciclo, asígnele un valor y luego use este número entero en un ciclo. –

3

Hay dos maneras de interpretar su pregunta:

  • es la multiplicación de 12332 * 324234 evaluado en cada bucle
  • es la expresión de la condición del bucle evaluado en cada bucle

El respuesta a esas dos, diferentes, preguntas son:

  • No, de hecho se evalúa en comp ile-tiempo, ya que implica dos constantes
  • Sí, si es necesario, son

En otras palabras:

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

Si la evaluación de someString.Length es costoso, tendrá un cargo de cada iteración de bucle

+0

Dudo que String.Length funcione como strlen. Sin embargo, 'someString' podría cambiar en cada ciclo de iteración. – Luca

7

Actualmente no se compilará porque se desbordará, pero si lo haces un número más pequeño y abres Reflector, encontrarás algo como esto.

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

} 
+1

Buen punto sobre el desbordamiento – KLee1

0

Como se señaló @Chaos, no compilará. Pero si usa una expresión representable (como 100 * 100), el resultado probablemente estará codificado. En Mono, el CIL incluye:

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 

Como se puede ver, 100 * 100 es modificable como 10000. Sin embargo, en general se evaluará cada vez, y si un método o propiedad se llama, este probablemente no pueda ser optimizado.

0

Parece que se calcula siempre. Desmontaje de 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 
+1

No hay comando de multiplicar en el desensamblaje. –

13

Este es uno de los comportamientos más comúnmente mal entendido de bucles en C#.

Esto es lo que necesita saber:

Loop grada cálculos, si no constante y la participación de una variable, propiedad de acceso, llamada de función, o delegar la invocación se recalcule el valor de los límites antes de cada iteración del ciclo.

Así, por ejemplo:

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

En este caso, la expresión 1234*1234 es un tiempo de compilación constante, y como resultado se no se vuelve a calcular en cada iteración. De hecho, se calcula en tiempo de compilación y se reemplaza con una constante.

Sin embargo, en este caso:

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

El valor de k tiene que ser examinada en cada iteración. Después de todo, puede cambiar .. en este ejemplo sí. Afortunadamente, dado que k es simplemente una variable local, el costo de acceder a ella es muy bajo, y en muchos casos se conservará en la memoria caché de la CPU local o incluso se mantendrá en un registro (dependiendo de cómo el JIT procese y emita el codigo de maquina).

En el caso de algo como lo siguiente:

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

El coste de cálculo de la sequence.Count() puede ser bastante caro. Y dado que se evalúa en cada iteración del ciclo, puede sumarse rápidamente.

El compilador no pueden optimizar de distancia las llamadas a métodos o propiedades que se producen dentro de la expresión fuera de bucle porque también pueden cambiar con cada iteración. Imagínese si el bucle anterior fue escrito como:

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

Claramente sequence está cambiando en cada iteración ... y por lo tanto la Count() es probable que sea diferente en cada iteración. El compilador no intenta realizar ningún análisis estático para determinar si la expresión de límites de bucle podría ser constante ... eso sería extremadamente complicado, si no imposible. En cambio, asume que si la expresión no es una constante, debe evaluarse en cada iteración.

Ahora, en la mayoría de los casos, el costo de calcular la restricción de límites para un ciclo va a ser relativamente económico, por lo que no tiene que preocuparse por ello. Pero necesita comprender cómo trata el compilador los límites de bucle como este. Además, como desarrollador , debe tener cuidado con el uso de propiedades o métodos que tengan efectos secundarios como parte de una expresión de límites - después de todo, estos efectos secundarios ocurrirán en cada iteración del ciclo.

-2

Sí, el valor de comparación se calcula cada ciclo de ciclo.

Si es absolutamente necesario utilizar para de bucle, que rara vez es realmente necesario ya, que yo sepa sólo hay una "buena" plantilla de bucle:

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

Aviso el incremento prefijo también.

+2

Esto está equivocado por muchas razones. No se trata de ser "necesario". Porque los bucles nunca son necesarios, pero a veces hay la manera más limpia de expresar algo. No todas las condiciones usan un método en absoluto. De hecho, el que está en la pregunta no. De los que sí lo hacen, a veces el método * debe * ser llamado cada vez, y en otros casos, la diferencia de rendimiento es insignificante. Finalmente, el incremento previo y posterior no hace ninguna diferencia práctica aquí. –

+0

Estoy bastante seguro de que el desarrollador que utiliza esta plantilla sabe cuándo debe llamarse el método cada vez y lo ajustará. De todos modos, es un buen estilo usar ese "patrón" que Scott Meyers introdujo en C++ y sigue siendo muy útil. – Grozz

+0

'++ i' y' i ++ 'no hacen ninguna diferencia porque el compilador los optimizará para que sean semánticamente idénticos. – KLee1

2

En primer lugar, el bucle for de la pregunta no se compilará. Pero digamos que era

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

     } 

VS

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

     } 

el código IL es exactamente el mismo.

.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 
} 

Ahora, incluso si reemplazo con una función en el bucle como

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; 
    } 

puedo obtener el código 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 
} 

que se parece a mí misma.

Me parece que no hay diferencia en FOR LOOP con límite finito, otro con límite de cálculo y el último con límite proveniente de una función.

Por lo tanto, siempre y cuando el código copie, usted sabe lo que está haciendo en un bucle de mamut como este y tiene suficiente memoria en proceso, creo que funcionará y producirá el mismo rendimiento. (en C#)

Cuestiones relacionadas