2009-01-21 17 views
40

¿Hay alguna diferencia de rendimiento entre el uso de algo así como¿Hay alguna diferencia de rendimiento entre ++ i e i ++ en C#?

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

y

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

o es el compilador capaz de optimizar de manera tal que son igual de rápido en el caso en que son funcionalmente equivalentes ?

Editar: Esto fue preguntado porque tuve una conversación con un compañero de trabajo al respecto, no porque creo que sea una optimización útil en ningún sentido práctico. Es en gran parte académica.

+1

Esto no debería haber sido cerrado. Pero de todos modos, si compila y usa ildasm.exe para mirar el MSIL, verá que los dos ejemplos dan como resultado MSIL idéntico. –

+0

Votaron para reabrir. A John Sheehan, tvanfosson, ctacke, Marc Gravell ... si está votando para cerrar ... ¡edite y proporcione el duplicado en la pregunta! No veo ningún enlace a un engañado – mmcdole

+0

Hubo una respuesta con el duplicado, no sé a dónde fue. –

Respuesta

34

No hay diferencia en el código intermedio generado para ++ i y i ++ en este caso. Teniendo en cuenta este programa:

class Program 
{ 
    const int counter = 1024 * 1024; 
    static void Main(string[] args) 
    { 
     for (int i = 0; i < counter; ++i) 
     { 
      Console.WriteLine(i); 
     } 

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

El código IL generado es el mismo para ambos bucles:

IL_0000: ldc.i4.0 
    IL_0001: stloc.0 
    // Start of first loop 
    IL_0002: ldc.i4.0 
    IL_0003: stloc.0 
    IL_0004: br.s  IL_0010 
    IL_0006: ldloc.0 
    IL_0007: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_000c: ldloc.0 
    IL_000d: ldc.i4.1 
    IL_000e: add 
    IL_000f: stloc.0 
    IL_0010: ldloc.0 
    IL_0011: ldc.i4  0x100000 
    IL_0016: blt.s  IL_0006 
    // Start of second loop 
    IL_0018: ldc.i4.0 
    IL_0019: stloc.0 
    IL_001a: br.s  IL_0026 
    IL_001c: ldloc.0 
    IL_001d: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0022: ldloc.0 
    IL_0023: ldc.i4.1 
    IL_0024: add 
    IL_0025: stloc.0 
    IL_0026: ldloc.0 
    IL_0027: ldc.i4  0x100000 
    IL_002c: blt.s  IL_001c 
    IL_002e: ret 

Dicho esto, es posible (aunque poco probable) que el compilador JIT puede hacer algunas optimizaciones en ciertos contextos eso favorecerá una versión sobre la otra. Sin embargo, si existe tal optimización, probablemente solo afectaría la iteración final (o tal vez la primera) de un ciclo.

En resumen, no habrá diferencia en el tiempo de ejecución del simple preincremento o incremento posterior de la variable de control en la construcción de bucle que ha descrito.

+0

++ Sí, no hay diferencia, e incluso si hubiera, el bucle tendría que estar casi vacío de código para notar la diferencia. En este código particular, la sobrecarga del ciclo es para el contenido del ciclo como "un rayo es para un rayo". (Cita de Mark Twain) –

0

Según this answer, i ++ usa una instrucción de CPU más que ++ i. Pero si esto resulta en una diferencia en el rendimiento, no lo sé.

Como cualquiera de los bucles se puede reescribir fácilmente para usar un incremento posterior o un incremento previo, supongo que el compilador siempre usará la versión más eficiente.

4

Chicos, chicos, las "respuestas" son para C y C++.

C# es un animal diferente.

Utilice ILDASM para ver la salida compilada para verificar si hay una diferencia MSIL.

7

Ah ... Abrir nuevamente. DE ACUERDO. Aquí está el trato.

ILDASM es un comienzo, pero no un final. La clave es: ¿qué generará el JIT para el código de ensamblaje?

Esto es lo que quieres hacer.

Tome un par de muestras de lo que está tratando de ver. Obviamente puedes cronometrarlos si quieres, pero supongo que quieres saber más que eso.

Esto es lo que no es obvio. El compilador de C# genera algunas secuencias de MSIL que no son óptimas en muchas situaciones. El JIT se ajustó para tratar con estos y peculiaridades de otros idiomas. El problema: solo se han ajustado los 'caprichos' que alguien ha notado.

Realmente desea hacer una muestra que tenga sus implementaciones para probar, regresa a main (o donde sea), Sleep() s, o algo donde pueda adjuntar un depurador, luego vuelva a ejecutar las rutinas.

NO QUIERES iniciar el código en el depurador o el JIT generará un código no optimizado, y parece que quieres saber cómo se comportará en un entorno real. El JIT hace esto para maximizar la información de depuración y minimizar la ubicación actual de la fuente de 'saltar alrededor'. Nunca inicie una evaluación de perf bajo el depurador.

OK. Entonces, una vez que el código se ha ejecutado una vez (es decir: el JIT ha generado un código para él), luego conecte el depurador durante la suspensión (o lo que sea). Luego observe el x86/x64 que se generó para las dos rutinas.

Mi instinto me dice que si está utilizando ++ i/i ++ como describió, es decir, en una expresión independiente donde el resultado de valor r no se vuelve a utilizar, no habrá ninguna diferencia. ¡Pero no será divertido ir a buscar y ver todas las cosas buenas! :)

+0

Eso es muy informativo . ¡Gracias! –

3

¿Tienen en mente una pieza concreta de código y liberación de CLR? Si es así, cotejarlo. Si no, olvídate de eso.Micro-optimización, y todo eso ... Además, ni siquiera puedes estar seguro de que una versión CLR diferente produzca el mismo resultado.

0
static void Main(string[] args) { 
    var sw = new Stopwatch(); sw.Start(); 
    for (int i = 0; i < 2000000000; ++i) { } 
    //int i = 0; 
    //while (i < 2000000000){++i;} 
    Console.WriteLine(sw.ElapsedMilliseconds); 

promedio de 3 carreras:
para con i ++: 1307 para con ++ i: 1314

mientras que con i ++: 1261 mientras que con ++ i: 1276

Esa es una Celeron D a 2,53 Ghz. Cada iteración tomó alrededor de 1,6 ciclos de CPU. Eso significa que la CPU estaba ejecutando más de 1 instrucción en cada ciclo o que el compilador JIT desenrolló los bucles. La diferencia entre i ++ y ++ i fue de solo 0,01 ciclos de CPU por iteración, probablemente debido a los servicios del sistema operativo en segundo plano.

+1

Vea cómo Davy Landman veces su código aquí (en el segundo bloque de código): http://stackoverflow.com/questions/419952/could-you-please-review-my-quick-int-parser-implementation#421693 Al establecer prioridad de proceso, afinidad y prioridad de subprocesos, puede obtener mediciones más precisas. –

2

Si está haciendo esta pregunta, está tratando de resolver el problema.

La primera pregunta es: "¿cómo puedo mejorar la satisfacción del cliente con mi software haciéndolo funcionar más rápido?" y la respuesta casi nunca es "use ++ i en lugar de i ++" o viceversa.

de la codificación posterior de horror "Hardware is Cheap, Programmers are Expensive":

Reglas de Optimización:
Regla 1: No practico.
Regla 2 (solo para expertos): No lo hagas aún.
- M.A. Jackson

leí el artículo 2 en el sentido de "primera escritura limpia código, claro que cumpla con las necesidades de sus clientes, a continuación, acelerarlo donde es demasiado lento". Es muy poco probable que ++i contra i++ sea la solución.

+1

Sorprendente, ¿verdad? La mejor respuesta aquí, y la única necesaria. –

+3

La pregunta "cuál es más rápido" nunca se me ocurrió. Siempre uso '++ i'. ¿Por qué? Porque leo * de izquierda a derecha * y '++ i' se lee como" incrementar el valor de 'i'" y no "tomar' i' e incrementar su valor ". Como '++' está en el lado izquierdo, puedo ver inmediatamente lo que está pasando, no necesito mover mis ojos a la derecha :) Si tienes un nombre de variable largo, '++ something' es mucho más legible . – Jabba

+3

-1: Respondiendo a una pregunta que el OP nunca hizo al ignorar la pregunta real. También llamado OP un troll. – JMD

3

Como Jim Mischel has shown, el compilador generará MSIL idéntico para las dos formas de escribir el for-loop.

Pero eso es todo: no hay ninguna razón para especular sobre el JIT o realizar mediciones de velocidad. Si las dos líneas de código generan MSIL idénticos, no solo se comportarán de manera idéntica, sino que son efectivamente idénticos.

Ningún posible JIT sería capaz de distinguir entre los bucles, por lo que el código de máquina generado necesariamente debe ser idéntico también.

2

Además de otras respuestas, puede haber una diferencia si su i no es un int. En C++, si es un objeto de una clase que tiene los operadores ++() y ++(int) sobrecargados, entonces puede hacer una diferencia, y posiblemente un efecto secundario. El rendimiento de ++idebería ser mejor en este caso (dependiendo de la implementación).

+0

buena respuesta de C++, muy mala respuesta de C#. IIRC, C# no tiene sobrecargas separadas para preincremento y postincremento. –

+0

@Ben Voigt: Sí, solo puede tener una sobrecarga para el operador de incremento en C# (http://msdn.microsoft.com/en-us/library/aa691363.aspx). No lo sabía. Gracias por el consejo. –

Cuestiones relacionadas