2010-07-30 19 views
12

una pregunta rápida y simple acerca de for-loops.C# - For-loop internals

Situación Actualmente estoy escribiendo un código de alto rendimiento cuando de repente me preguntaba cómo se comporta realmente el for-loop. Sé que me he tropezado con esto antes, pero no puedo por la vida de encontrar esta información otra vez:/

Aún así, mi principal preocupación era con el limitador. Digamos que tenemos:

for(int i = 0; i < something.awesome; i++) 
{ 
// Do cool stuff 
} 

Pregunta ¿Se something.awesome almacena como una variable interna o es el bucle constante recogiéndolos something.awesome hacer la lógica a comprobar? Por supuesto, lo que estoy preguntando es porque necesito revisar un montón de cosas indexadas y realmente no quiero la función adicional: llamar por encima de cada pase.

Sin embargo, si something.awesome solo se llama una vez, ¡entonces voy a regresar bajo mi roca feliz! :)

+4

Bueno, pruébalo con 'for (int i = 0; i <(i + 2); ++ i)';) – Dario

+4

Solo un lado; there * is * es un escenario donde el JIT hace algo inteligente aquí; con matrices detecta el caso común de '

+0

Hmm, en realidad no confío mucho en el JIT ya que estoy desarrollando XNA, que es un poco peculiar en el 360. Sin embargo, aunque no sea poco apreciado, podría ser útil para PC-apps. :) – Vectovox

Respuesta

20

Se puede utilizar un programa de ejemplo sencillo para comprobar el comportamiento:

using System; 

class Program 
{ 
    static int GetUpperBound() 
    { 
     Console.WriteLine("GetUpperBound called."); 
     return 5; 
    } 

    static void Main(string[] args) 
    { 
     for (int i = 0; i < GetUpperBound(); i++) 
     { 
      Console.WriteLine("Loop iteration {0}.", i); 
     } 
    } 
} 

La salida es la siguiente:

GetUpperBound called. 
Loop iteration 0. 
GetUpperBound called. 
Loop iteration 1. 
GetUpperBound called. 
Loop iteration 2. 
GetUpperBound called. 
Loop iteration 3. 
GetUpperBound called. 
Loop iteration 4. 
GetUpperBound called. 

Los detalles de este comportamiento se describen en la Especificación del lenguaje C# 4.0, sección 8.3.3 (Encontrará e spec dentro C: \ Archivos de programa \ Microsoft Visual Studio 10.0 \ VC# \ Especificaciones \ 1033):

Una sentencia for se ejecuta como sigue:

  • Si un para- el inicializador está presente, los iniciadores de variables o las expresiones se ejecutan en el orden se escriben. Este paso es solo realizado una vez.

  • Si hay una condición forzada presente, se evalúa.

  • Si la condición forzada no está presente o si la evaluación es cierta, el control se transfiere a la instrucción incrustada . Cuando y si alcances de control el punto final de la instrucción incrustada (posiblemente de ejecución de una declaración sigue), las expresiones del para-iterador, si los hay, están evaluaron en secuencia, y luego otra iteración se realizado, comenzando con la evaluación de para-condición en el paso anterior.

  • Si el para-condición está presente y la evaluación produce falsa, control se transfiere al punto final de la para comunicado.

+0

¡Gracias por la información extra! ¡No tenía idea de que la respuesta estaba frente a mí todo el tiempo! :) – Vectovox

4

Se evaluó cada vez. Pruebe esto en un simple aplicación de consola:

public class MyClass 
{ 
    public int Value 
    { 
     get 
     {     
      Console.WriteLine("Value called"); 
      return 3; 
     } 
    } 
} 

utilizado de esta manera:

MyClass myClass = new MyClass(); 
for (int i = 0; i < myClass.Value; i++) 
{     
} 

dará lugar a tres líneas impresas en la pantalla.

actualización

Así que para evitar esto, se podía esto:

int awesome = something.awesome; 
for(int i = 0; i < awesome; i++) 
{ 
// Do cool stuff 
} 
1

something.awesome será reevaluado cada vez que vaya a través del bucle.

Sería mejor hacer esto:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
} 
1

Cada vez que el compilador retrive el valor de something.awesome y evaluarla

1

La condición se evalúa cada vez, incluyendo la recuperación del valor de something.awesome. Si desea evitar eso, establezca una variable de temperatura en something.awesome y compare con la variable de temperatura en su lugar.

6

Si something.awesome es un campo , es probable que se acceda cada vez al ciclo ya que algo en el cuerpo del ciclo puede actualizarlo. Si el cuerpo del bucle es lo suficientemente simple y no llama a ningún método (aparte de los métodos que el compilador en línea), entonces el compilador puede probar que es seguro poner el valor de algo.awesome en un registro. Los escritores del compilador acostumbraban a permitirse el lujo de hacer esto corto de cosas.

Sin embargo, actualmente se necesita mucho tiempo para acceder a un valor desde la memoria principal, pero una vez que el valor ha sido leído por primera vez, es dirigido por la CPU. Leer el valor por segunda vez desde la memoria caché de la CPU tiene una velocidad mucho más cercana a la lectura de un registro que a la lectura desde la memoria principal. No es raro que una memoria caché de la CPU sea cientos de veces más rápida que la memoria principal.

Ahora bien, si something.awesome es una propiedad , entonces es de hecho una llamada al método. El compilador llamará al método cada vez que pasa el ciclo. Sin embargo, si la propiedad/método es solo unas pocas líneas de código, puede estar en línea por el compilador. Inlinear es cuando el compilador ingresa una copia del código del método directamente en lugar de llamar al método, por lo que una propiedad que solo devuelve el valor de un campo se comportará igual que el ejemplo de campo anterior.

Evan cuando la propiedad no está en línea, será en el caché de CPU después de haber sido llamada la primera vez. Así que si es muy complejo o el ciclo se repite muchas veces tarda mucho más en llamar la primera vez en el ciclo, quizás por más de un factor de 10.

En los viejos tiempos, it Solía ​​ser fácil porque todo el acceso a la memoria y las acciones de la CPU tomaban casi el mismo tiempo. En estos días, la memoria caché del procesador puede cambiar fácilmente el tiempo para algunos accesos a la memoria y llamadas a métodos por con un factor de 100. Los perfiladores tienden a suponer que todo el acceso a la memoria toma el mismo tiempo. Entonces, si su perfil se le indicará que haga cambios que pueden no tener ningún efecto en el mundo real.

Cambiar el código para:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
} 

Will en algunos casos se extienden hacia arriba, sino que también hace que sea más compleja. Sin embargo

int limit = myArray.length; 
for(int i = 0; i < limit; i++) 
{ 
    myArray[i[ = xyn; 
} 

es más lento entonces

for(int i = 0; i < myArray.length; i++) 
{ 
    myArray[i[ = xyn; 
} 

como .NET comprobar el atado de las matrices de cada vez que se accede a ellos y tienen lógica para eliminar la marca cuando el bucle es lo suficientemente simple.

Por lo tanto, es mejor mantener el código simple y claro hasta que pueda demostrar que hay un problema. Usted gana mucho mejor si dedica tiempo a mejorar el diseño general de un sistema, esto es fácil de hacer si el código con el que comienza es simple.

+0

¡Muchas gracias por la gran lectura! En mi situación actual, de hecho era una matriz, sin embargo, he encontrado instancias antes en las que no ha sido una, por ejemplo. propiedades. Pero tomaré "mantenerlo limpio" bajo consideración. ¡En segundo lugar, adivinar mis decisiones es, después de todo, contraproducente! :) – Vectovox

0

Guardaría alguna variable y la borraría después de su uso.

usando (int límite = something.awesome)
{
for (int i = 0; i < limiti; i ++)
{
// Código.
}}

De esta manera no va a comprobar en todo momento.