¿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!
}
¿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!
}
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
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. –
Hay dos maneras de interpretar su pregunta:
El respuesta a esas dos, diferentes, preguntas 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
Dudo que String.Length funcione como strlen. Sin embargo, 'someString' podría cambiar en cada ciclo de iteración. – Luca
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++)
{
}
Buen punto sobre el desbordamiento – KLee1
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.
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
No hay comando de multiplicar en el desensamblaje. –
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.
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.
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í. –
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
'++ i' y' i ++ 'no hacen ninguna diferencia porque el compilador los optimizará para que sean semánticamente idénticos. – KLee1
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#)
posible duplicado de [C# - For-loop internals] (http://stackoverflow.com/questions/3369773/c-for-loop-internals) –
@Darin, no realmente. Eso usó un método/propiedad, no una constante. –
Mal ejemplo; el compilador optimizará cálculos estáticos como esos donde los encuentre. –