2010-07-20 13 views
11
List<int> list = ... 

for(int i = 0; i < list.Count; ++i) 
{ 
      ... 
} 

El compilador conoce la lista. ¿No es necesario llamar al conteo cada iteración?¿El compilador de C# optimiza las propiedades de recuento?

+0

Es llamar a una variable, tal como lo haría si lo hiciera 'int count = list.Count' y utilizó ese lugar. Estoy confundido por tu pregunta? –

+0

@ Nathan: En realidad está llamando a una propiedad, que es, en efecto, una llamada a un método, no una obtención de variables. Depende del JIT optimizar esto (no el compilador de C#, por lo general), pero no sucede. –

+0

Bueno, usando un bucle for como ese, puede ser necesario porque puedes modificar la colección. –

Respuesta

21

¿Estás seguro de eso?

List<int> list = new List<int> { 0 }; 

for (int i = 0; i < list.Count; ++i) 
{ 
    if (i < 100) 
    { 
     list.Add(i + 1); 
    } 
} 

Si el compilador almacena en caché la propiedad Count anterior, el contenido de list sería 0 y 1. Si no fuera así, los contenidos serían los números enteros de 0 a 100.

Ahora, que el poder parece un ejemplo artificial para ti; pero ¿y este?

List<int> list = new List<int>(); 

int i = 0; 
while (list.Count <= 100) 
{ 
    list.Add(i++); 
} 

Puede parecer como si estos dos fragmentos de código son completamente diferentes, pero eso es sólo debido a la forma en que la tendencia pensar sobre for bucles frente while bucles. En cualquier caso, el valor de una variable se verifica en cada iteración. Y en cualquier caso, ese valor muy bien podría cambiar.

Normalmente no es seguro asumir que el compilador optimiza algo cuando el comportamiento entre versiones "optimizadas" y "no optimizadas" del mismo código es realmente diferente.

+0

Esto me hace perder los viejos tiempos de C++ 'const'. –

+1

1 para el buen ejemplo :) –

+0

IIRC, el primer ejemplo no se compile, no puedes modificar una lista de al iterar sobre ella. –

9

El compilador de C# no realiza ninguna optimización como esta. El compilador JIT, sin embargo, optimiza esto para las matrices, creo (que no son redimensionables), pero no para las listas.

Una propiedad de recuento de lista puede cambiar dentro de la estructura de bucle, por lo que sería una optimización incorrecta.

0

No, no es así. Porque la condición se calcula en cada paso. Puede ser más complejo que la simple comparsion con el recuento, y se permite que cualquier expresión booleana:

for(int i = 0; new Random().NextDouble() < .5d; i++) 
    Console.WriteLine(i); 

http://msdn.microsoft.com/en-us/library/aa664753(VS.71).aspx

0

que depende de la implementación particular del conde; Nunca he notado ningún problema de rendimiento con el uso de la propiedad Count en una lista, así que supongo que está bien.

En este caso puedes ahorrarte un rato escribiendo con un foreach.

List<int> list = new List<int>(){0}; 
foreach (int item in list) 
{ 
    // ... 
} 
+0

A menos que use el índice 'i' en algún lugar del cuerpo del bucle. Algo tan simple como agregar uno a cada elemento no se puede hacer con 'foreach'. –

+0

¿El OP no menciona agregar algo a cada elemento? –

1

Para todos los otros comentaristas que dicen que la propiedad 'conde' podía cambio en un cuerpo del ciclo: optimizaciones JIT le permiten tomar ventaja del código real que se está ejecutando, no es el peor de los casos de lo que podría suceder. En general, el Conde podría cambiar. Pero no en todo el código.

Por lo tanto, en el ejemplo del póster (que puede no tener ningún cambio de cuenta), ¿no es razonable que el JIT detecte que el código en el bucle no cambia la variable interna que List utiliza para mantener su longitud? Si detecta que list.Count es constante, ¿no podría levantar ese acceso variable fuera del cuerpo del bucle?

No sé si el JIT hace esto o no. Pero no soy tan rápido para eliminar este problema como trivialmente "nunca"."

+0

Esto podría ser arbitrariamente difícil, ya que puede tener una referencia a la lista en otro hilo, que luego se modifica mientras está en el ciclo del hilo actual. De acuerdo, esto llevaría a una gran cantidad de otros problemas, pero el problema principal es que la lista es mutable y, por lo tanto, no se puede garantizar el estado del recuento de una iteración a otra. Esto es cierto incluso si ignora el enhebrado (¿debe la fluctuación de fase examinar cada llamada recursiva para verificar si hay efectos secundarios?) –

+0

Sí, puede ser difícil de determinar. El objetivo de mi publicación es que * a veces *, puede ser fácil. Y cuando es fácil, podría optimizarse. En cuanto al enhebrado, si la Lista no está protegida por un candado al realizar el recorrido, incluso la versión no optimizada tiene errores. – Karmastan

+0

Nadie dijo "nunca". El OP preguntó: "¿el compilador conoce la lista. No se debe llamar al conteo cada iteración?" a lo que respondí: "¿Estás seguro?" Me pareció que el OP estaba dando por hecho que la propiedad 'Count' * no * cambiaría; mi intención era desafiar esa suposición. –

3

Si se echa un vistazo a la IL generada, por ejemplo, de Dan Tao se verá una línea como esta en la condición del bucle:

callvirt instance int32 [mscorlib]System.Collections.Generic.List`1<int32>::get_Count() 

Ésta es una prueba irrefutable de que el conde (es decir get_Count () se llama para cada iteración del ciclo.

+0

Mis habilidades de ensamblaje x86 son extremadamente oxidadas, pero una investigación de los resultados JIT muestra una 'llamada FFFFFFFFF6BA91F0' que podría indicar una llamada a get_Count() – AlfredBr

+4

No, el compilador JIT lo convierte en una carga directa de registro de CPU. Se llama "inline". Los captadores de propiedad pequeños siempre están incluidos en la compilación Release. Tienes que echarle un vistazo al código de máquina optimizado para verlo. –

+2

Correcto, el IL no es la última palabra sobre lo que realmente sucede. –

4

Vale la pena señalar, como nadie más lo ha mencionado, que no se puede saber al mirar un ciclo como este lo que realmente hará la propiedad "Count", o qué efectos secundarios que pueda tener.

Considere lo siguiente cas es:

  • Una implementación de un tercero de una propiedad llamada "Count" podría ejecutar cualquier código que desee. p.ej. devuelve un número aleatorio para todo lo que sabemos. Con List podemos estar un poco más seguros de cómo funcionará, pero ¿cómo es el JIT para diferenciar estas implementaciones?

  • Cualquier llamada a un método dentro del bucle podría potencialmente alterar el valor de retorno de la cuenta (no sólo una recta "Añadir" directamente en la colección, sino un método de usuario que se llama en el bucle podría también es parte de la colección)

  • Cualquier otro hilo que se ejecute simultáneamente también podría cambiar el valor de Recuento.

El JIT simplemente no puede "saber" que Count es constante.

Sin embargo, el compilador JIT puede hacer que el código se ejecute mucho más eficientemente al al interior la implementación de la propiedad Count (siempre que sea una implementación trivial). En su ejemplo, puede ser una prueba simple de un valor de variable, evitando la sobrecarga de una llamada de función en cada iteración, y haciendo que el código final sea agradable y rápido. (Nota: No sé si el JIT se hacer esto, sólo que podría yo no me importa - véase la última frase de mi respuesta a averiguar por qué.)

Pero incluso con En línea, el valor aún se puede cambiar entre las iteraciones del ciclo, por lo que aún tendría que leerse desde la RAM para cada comparación. Si tuviera que copiar el conteo en una variable local y el JIT podría determinar mirando el código en el ciclo que la variable local se mantendrá constante durante la vida útil del ciclo, entonces podrá optimizarlo aún más (por ejemplo, manteniendo la constante valor en un registro en lugar de tener que leerlo desde la RAM en cada iteración). Entonces, si usted (como programador) sabe que el recuento será constante durante la vida útil del ciclo, es posible que pueda ayudarlo almacenando el recuento en una variable local. Esto le da al JIT la mejor oportunidad de optimizar el bucle. (Pero no hay garantías de que el JIT aplicará esta optimización, por lo que puede ser indiferente el tiempo de ejecución para "optimizar" manualmente de esta manera. También corre el riesgo de que las cosas vayan mal si su suposición (que Count es constante) es incorrecta . O su código puede romperse si otro programador edita el contenido del ciclo para que Count ya no sea constante y no detecta su astucia)

Así que la moraleja de la historia es: El JIT puede hacer una bonita buena puñalada para optimizar este caso mediante la creación de líneas. Incluso si no lo hace ahora, puede hacerlo con la próxima versión de C#. Puede que no ganes ninguna ventaja "codificando" manualmente el código, y te arriesgas a cambiar su comportamiento y romperlo, o al menos hacer que el mantenimiento futuro de tu código sea más arriesgado, o posiblemente perder futuras mejoras JIT.Por lo que el mejor enfoque es simplemente escribir de la forma que tiene, y optimizarlo cuando el perfilador te dice que el bucle es su cuello de botella.

Por lo tanto, en mi humilde opinión es interesante considerar/entender casos como este, pero en última instancia, que en realidad no necesita saber. Un poco de conocimiento puede ser algo peligroso. Simplemente deje que el JIT haga lo suyo, y luego perfile el resultado para ver si necesita mejorar.

Cuestiones relacionadas