Pregunta

¿Existe alguna diferencia de rendimiento entre i++ y ++i si el valor resultante no se utiliza?

¿Fue útil?

Solución

Resumen ejecutivo:No.

i++ potencialmente podría ser más lento que ++i, ya que el antiguo valor de iEs posible que deba guardar para su uso posterior, pero en la práctica todos los compiladores modernos optimizarán esto.

Podemos demostrar esto mirando el código para esta función, ambos con ++i y i++.

$ cat i++.c
extern void g(int i);
void f()
{
    int i;

    for (i = 0; i < 100; i++)
        g(i);

}

Los archivos son los mismos, excepto por ++i y i++:

$ diff i++.c ++i.c
6c6
<     for (i = 0; i < 100; i++)
---
>     for (i = 0; i < 100; ++i)

Los compilaremos y también obtendremos el ensamblador generado:

$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c

Y podemos ver que tanto el objeto generado como los archivos ensambladores son los mismos.

$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22

Otros consejos

De Eficiencia versus intención por Andrew Koenig:

  

Primero, está lejos de ser obvio que ++i es más eficiente que i++, al menos en lo que respecta a las variables enteras.

Y:

  

Entonces, la pregunta que uno debería hacerse no es cuál de estas dos operaciones es más rápida, sino cuál de estas dos operaciones expresa con mayor precisión lo que está tratando de lograr. Presento que si no está utilizando el valor de la expresión, nunca hay una razón para usar <=> en lugar de <=>, porque nunca hay una razón para copiar el valor de una variable, incrementar la variable y luego tirar la copia.

Entonces, si no se usa el valor resultante, usaría <=>. Pero no porque sea más eficiente: porque indica correctamente mi intención.

Una mejor respuesta es que ++i a veces será más rápido pero nunca más lento.

Todo el mundo parece estar asumiendo que i es un tipo integrado regular como int. En este caso no habrá una diferencia medible.

Sin embargo, si i++ es de tipo complejo, puede encontrar una diferencia apreciable. Para ++it debe hacer una copia de su clase antes de incrementarla. Dependiendo de lo que esté involucrado en una copia, podría ser más lento, ya que con <=> puede devolver el valor final.

Foo Foo::operator++()
{
  Foo oldFoo = *this; // copy existing value - could be slow
  // yadda yadda, do increment
  return oldFoo;
}

Otra diferencia es que con <=> tiene la opción de devolver una referencia en lugar de un valor. Nuevamente, dependiendo de lo que esté involucrado en hacer una copia de su objeto, esto podría ser más lento.

Un ejemplo del mundo real de dónde puede ocurrir esto sería el uso de iteradores. Copiar un iterador es poco probable que sea un cuello de botella en su aplicación, pero sigue siendo una buena práctica acostumbrarse a usar <=> en lugar de <=> donde el resultado no se ve afectado.

Tomando una hoja de Scott Meyers, Elemento de C ++ más eficaz 6: Distinga entre las formas de prefijo y postfijo de las operaciones de incremento y decremento .

La versión del prefijo siempre se prefiere sobre el postfix en lo que respecta a los objetos, especialmente en lo que respecta a los iteradores.

La razón de esto si observa el patrón de llamada de los operadores.

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

// Postfix
const Integer Integer::operator++(int)
{
    Integer oldValue = *this;
    ++(*this);
    return oldValue;
}

Mirando este ejemplo, es fácil ver cómo el operador de prefijo siempre será más eficiente que el postfix. Debido a la necesidad de un objeto temporal en el uso del postfix.

Esta es la razón por la cual cuando ve ejemplos que usan iteradores, siempre usan la versión de prefijo.

Pero como señala para int, efectivamente no hay diferencia debido a la optimización del compilador que puede tener lugar.

Aquí hay una observación adicional si le preocupa la micro optimización. Los bucles decrecientes pueden ser 'posiblemente' más eficientes que los bucles incrementales (dependiendo de la arquitectura del conjunto de instrucciones, por ejemplo, ARM), dado:

for (i = 0; i < 100; i++)

En cada ciclo, tendrá una instrucción para cada uno:

  1. Agregando 1 a i.
  2. Compare si 100 es menor que un Z==0.
  3. Una rama condicional si <=> es menor que un <=>.

Mientras que un ciclo decreciente:

for (i = 100; i != 0; i--)

El ciclo tendrá una instrucción para cada uno de:

  1. Decremento <=>, configurando el indicador de estado del registro de la CPU.
  2. Una rama condicional según el estado del registro de la CPU (<=>).

¡Por supuesto, esto funciona solo cuando disminuye a cero!

Recordado en la Guía del desarrollador del sistema ARM.

Respuesta corta:

Nunca hay diferencia entre i++ y ++i en términos de velocidad.Un buen compilador no debería generar código diferente en los dos casos.

Respuesta larga:

Lo que todas las demás respuestas no mencionan es que la diferencia entre ++i versus i++ sólo tiene sentido dentro de la expresión en la que se encuentra.

En el caso de for(i=0; i<n; i++), el i++ está solo en su propia expresión:hay un punto de secuencia antes del i++ y hay uno después.Por lo tanto, el único código de máquina generado es "aumentar i por 1" y está bien definido cómo se secuencia esto en relación con el resto del programa.Entonces, si lo cambiaras a prefijo ++, no importaría en lo más mínimo, todavía obtendrías el código de máquina "aumentar i por 1".

Las diferencias entre ++i y i++ Sólo importa en expresiones como array[i++] = x; versus array[++i] = x;.Algunos pueden argumentar y decir que el sufijo será más lento en tales operaciones porque el registro donde i Las residencias deben recargarse más tarde.Pero luego tenga en cuenta que el compilador es libre de ordenar sus instrucciones de la forma que desee, siempre y cuando no "rompa el comportamiento de la máquina abstracta", como lo llama el estándar C.

Entonces, si bien puedes asumir que array[i++] = x; se traduce a código de máquina como:

  • Valor de tienda de i en el registro A.
  • Almacene la dirección de la matriz en el registro B.
  • Sume A y B, almacene los resultados en A.
  • En esta nueva dirección representada por A, almacene el valor de x.
  • Valor de tienda de i en el registro A // ineficiente porque hay instrucciones adicionales aquí, ya hicimos esto una vez.
  • Registro de incremento A.
  • Registro de tienda A en i.

el compilador también podría producir el código de manera más eficiente, como por ejemplo:

  • Valor de tienda de i en el registro A.
  • Almacene la dirección de la matriz en el registro B.
  • Sume A y B, almacene los resultados en B.
  • Registro de incremento A.
  • Registro de tienda A en i.
  • ...// resto del código.

Sólo porque usted, como programador de C, está capacitado para pensar que el sufijo ++ sucede al final, el código de máquina no tiene que ordenarse de esa manera.

Entonces no hay diferencia entre prefijo y postfijo. ++ Cª.Ahora bien, lo que usted, como programador de C, debería tener en cuenta es la gente que usa prefijo de manera inconsistente en algunos casos y postfijo en otros casos, sin ningún motivo.Esto sugiere que no están seguros de cómo funciona C o que tienen un conocimiento incorrecto del idioma.Esto siempre es una mala señal, ya que a su vez sugiere que están tomando otras decisiones cuestionables en su programa, basadas en supersticiones o "dogmas religiosos".

"Prefijo ++ siempre es más rápido" es de hecho uno de esos falsos dogmas que es común entre los aspirantes a programadores de C.

Por favor, no deje que la pregunta de " cuál es más rápido " ser el factor decisivo para usar. Es probable que nunca le importe tanto, y además, el tiempo de lectura del programador es mucho más costoso que el tiempo de máquina.

Use el que tenga más sentido para el humano que lee el código.

En primer lugar:La diferencia entre i++ y ++i es insignificante en C.


A los detalles.

1.El conocido problema de C++: ++i es más rápido

En C++, ++i es más eficiente si y así i es una especie de objeto con un operador de incremento sobrecargado.

¿Por qué?
En ++i, el objeto primero se incrementa y posteriormente se puede pasar como referencia constante a cualquier otra función.Esto no es posible si la expresión es foo(i++) porque ahora el incremento debe hacerse antes foo() se llama, pero el valor anterior debe pasarse a foo().En consecuencia, el compilador se ve obligado a hacer una copia de i antes de ejecutar el operador de incremento en el original.Las llamadas adicionales al constructor/destructor son la parte mala.

Como se señaló anteriormente, esto no se aplica a los tipos fundamentales.

2.El hecho poco conocido: i++ puede se más rápido

Si no es necesario llamar a ningún constructor/destructor, que siempre es el caso en C, ++i y i++ debería ser igualmente rápido, ¿verdad?No.Son prácticamente igual de rápidos, pero puede haber pequeñas diferencias, que la mayoría de los que respondieron entendieron mal.

¿Cómo puede i++ ¿se más rápido?
El punto son las dependencias de datos.Si es necesario cargar el valor desde la memoria, es necesario realizar dos operaciones posteriores con él, incrementarlo y usarlo.Con ++i, es necesario realizar el incremento antes el valor se puede utilizar.Con i++, el uso no depende del incremento y la CPU puede realizar la operación de uso en paralelo a la operación de incremento.La diferencia es como máximo un ciclo de CPU, por lo que es realmente insignificante, pero está ahí.Y es al revés de lo que muchos esperarían.

@Mark Aunque el compilador puede optimizar la copia temporal (basada en pila) de la variable y gcc (en versiones recientes) lo está haciendo, no significa que todos los compiladores siempre lo harán.

Lo acabo de probar con los compiladores que utilizamos en nuestro proyecto actual y 3 de cada 4 no lo optimizan.

Nunca suponga que el compilador lo hace bien, especialmente si el código posiblemente más rápido, pero nunca más lento, es tan fácil de leer.

Si no tiene una implementación realmente estúpida de uno de los operadores en su código:

Alwas prefiere ++ i sobre i ++.

En C, el compilador generalmente puede optimizarlos para que sean los mismos si el resultado no se usa.

Sin embargo, en C ++ si se usan otros tipos que proporcionan sus propios operadores ++, es probable que la versión del prefijo sea más rápida que la versión postfix. Entonces, si no necesita la semántica de postfix, es mejor usar el operador de prefijo.

Puedo pensar en una situación en la que postfix es más lento que el incremento de prefijo:

Imagine que se utiliza un procesador con registro A como acumulador y es el único registro utilizado en muchas instrucciones (algunos microcontroladores pequeños son realmente así).

Ahora imagine el siguiente programa y su traducción en un ensamblaje hipotético:

Incremento de prefijo:

a = ++b + c;

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

Incremento de postfix:

a = b++ + c;

; load b
LD    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

Observe cómo se obligó a recargar el valor de b. Con el incremento de prefijo, el compilador puede simplemente incrementar el valor y seguir usándolo, posiblemente evitando volver a cargarlo ya que el valor deseado ya está en el registro después del incremento. Sin embargo, con el incremento de postfix, el compilador tiene que lidiar con dos valores, uno el antiguo y otro el valor incrementado que, como muestro arriba, da como resultado un acceso de memoria más.

Por supuesto, si no se utiliza el valor del incremento, como una sola instrucción i++;, el compilador puede (y lo hace) simplemente generar una instrucción de incremento independientemente del uso de postfix o prefijo.


Como nota al margen, me gustaría mencionar que una expresión en la que hay un b++ no puede convertirse simplemente en una con ++b sin ningún esfuerzo adicional (por ejemplo, agregando un - 1). Entonces, comparar los dos si son parte de alguna expresión no es realmente válido. A menudo, cuando usa a = b++ + 1; dentro de una expresión, no puede usar a = ++b;, por lo que incluso si <=> fuera potencialmente más eficiente, simplemente estaría mal. Por supuesto, la excepción es si la expresión lo está pidiendo (por ejemplo, <=> que se puede cambiar a <=>).

Siempre prefiero el pre-incremento, sin embargo ...

Quería señalar que incluso en el caso de llamar a la función operator ++, el compilador podrá optimizar lo temporal si la función se alinea. Dado que el operador ++ suele ser corto y a menudo implementado en el encabezado, es probable que se inserte.

Entonces, para fines prácticos, es probable que no haya mucha diferencia entre el rendimiento de las dos formas. Sin embargo, siempre prefiero el incremento previo, ya que parece mejor expresar directamente lo que & "; Estoy tratando de decir, en lugar de confiar en el optimizador para resolverlo.

Además, dar menos posibilidades al optimizador significa que el compilador se ejecuta más rápido.

Mi C está un poco oxidada, así que me disculpo de antemano. Speedwise, puedo entender los resultados. Pero estoy confundido sobre cómo ambos archivos salieron al mismo hash MD5. Tal vez un bucle for se ejecuta igual, pero ¿las siguientes 2 líneas de código no generarían un ensamblaje diferente?

myArray[i++] = "hello";

vs

myArray[++i] = "hello";

El primero escribe el valor en la matriz, luego incrementa i. Los segundos incrementos luego escribo en la matriz. No soy un experto en ensamblaje, pero simplemente no veo cómo se generaría el mismo ejecutable con estas 2 líneas de código diferentes.

Solo mis dos centavos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top