¿Es x + = a más rápido que x = x + a?

84

Estaba leyendo "El lenguaje de programación C ++" de Stroustrup, donde dice que hay dos formas de agregar algo a una variable

x = x + a;

y

x += a;

Él prefiere +=porque probablemente esté mejor implementado. Creo que quiere decir que también funciona más rápido.
¿Pero realmente es así? Si depende del compilador y otras cosas, ¿cómo lo compruebo?

Chiffa
fuente
45
"El lenguaje de programación C ++" se publicó por primera vez en 1985. La versión más reciente se publicó en 1997 y una edición especial de la versión de 1997 se publicó en 2000. Como consecuencia, algunas partes están muy desactualizadas.
JoeG
5
Las dos líneas podrían potencialmente hacer algo completamente diferente. Tienes que ser más específico.
Kerrek SB
26
Los compiladores modernos son lo suficientemente inteligentes como para que estas preguntas se consideren "obsoletas".
gd1
2
Reabrió esto porque la pregunta duplicada se refiere a C, no a C ++.
Kev

Respuestas:

212

Cualquier compilador vale su sal generará exactamente la misma secuencia en lenguaje de máquina para ambas construcciones para cualquier tipo incorporado ( int, float, etc.), siempre y cuando la declaración realmente es tan simple como x = x + a; y optimización está activada . (En particular, GCC -O0, que es el modo predeterminado, realiza anti-optimizaciones , como insertar almacenes completamente innecesarios en la memoria, para garantizar que los depuradores siempre puedan encontrar valores de variables).

Sin embargo, si la declaración es más complicada, pueden ser diferentes. Supongamos que fes una función que devuelve un puntero, entonces

*f() += a;

llamadas fsolo una vez, mientras que

*f() = *f() + a;

lo llama dos veces. Si ftiene efectos secundarios, uno de los dos estará mal (probablemente el último). Incluso si fno tiene efectos secundarios, es posible que el compilador no pueda eliminar la segunda llamada, por lo que esta última puede ser más lenta.

Y dado que estamos hablando de C ++ aquí, la situación es completamente diferente para los tipos de clases que sobrecargan operator+y operator+=. Si xes de ese tipo, entonces, antes de la optimización, se x += atraduce en

x.operator+=(a);

mientras que se x = x + atraduce en

auto TEMP(x.operator+(a));
x.operator=(TEMP);

Ahora, si la clase está escrita correctamente y el optimizador del compilador es lo suficientemente bueno, ambos terminarán generando el mismo lenguaje de máquina, pero no es tan seguro como lo es para los tipos integrados. Esto es probablemente en lo que está pensando Stroustrup cuando fomenta su uso +=.

zwol
fuente
14
También hay otro aspecto: la legibilidad . El lenguaje de C ++ para agregar expra varis var+=expry escribirlo de otra manera confundirá a los lectores.
Tadeusz Kopec
21
Si se encuentra escribiendo, *f() = *f() + a;es posible que desee analizar detenidamente lo que realmente está tratando de lograr ...
Adam Davis
3
Y si var = var + expr te confunde, pero var + = expr no, eres el ingeniero de software más extraño que he conocido. Ambos son legibles; solo asegúrate de ser consistente (y todos usamos op =, por lo que es discutible de todos modos = P)
WhozCraig
7
@PiotrDobrogost: ¿Qué hay de malo en responder preguntas? En cualquier caso, debe ser el interrogador quien verifique si hay duplicados.
Gorpik
4
@PiotrDobrogost me parece que estás un poco ... celoso ... Si quieres andar buscando duplicados, hazlo. Yo, por mi parte, prefiero responder las preguntas en lugar de buscar incautos (a menos que sea una pregunta que específicamente recuerdo haber visto antes). A veces puede ser más rápido y, ergo, ayudar a quien hizo la pregunta más rápido. Además, tenga en cuenta que esto ni siquiera es un bucle. 1es una constante, apuede ser volátil, un tipo definido por el usuario o lo que sea. Completamente diferente. De hecho, no entiendo cómo se cerró esto.
Luchian Grigore
56

Puede comprobarlo mirando el desmontaje, que será el mismo.

Para los tipos básicos , ambos son igualmente rápidos.

Esta es la salida generada por una compilación de depuración (es decir, sin optimizaciones):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

Para tipos definidos por el usuario , donde puede sobrecargar operator +y operator +=, depende de sus respectivas implementaciones.

Luchian Grigore
fuente
1
No es cierto en todos los casos. Descubrí que puede ser más rápido cargar una dirección de memoria en un registro, incrementarla y volver a escribirla que incrementar la ubicación de la memoria directamente (sin usar atomics). Intentaré mezclar el código ...
James
¿Qué hay de los tipos definidos por el usuario? Los buenos compiladores deberían generar un ensamblaje equivalente, pero no existe tal garantía.
mfontanini
1
@LuchianGrigore No, si aes 1 y xes volatileel compilador podría generar inc DWORD PTR [x]. Esto es lento.
James
1
@Chiffa no depende del compilador, sino del desarrollador. Puede implementar operator +para no hacer nada y operator +=calcular el número primo 100000 y luego regresar. Por supuesto, sería una tontería, pero es posible.
Luchian Grigore
3
@James: si su programa es sensible a la diferencia de rendimiento entre ++xy temp = x + 1; x = temp;, lo más probable es que deba escribirse en ensamblador en lugar de c ++ ...
EmirCalabuch
11

¡Si! Es más rápido de escribir, más rápido de leer y más rápido de entender, para lo último en el caso de que xpueda tener efectos secundarios. Entonces, en general, es más rápido para los humanos. El tiempo humano en general cuesta mucho más que el tiempo de la computadora, así que eso debe ser lo que preguntaba. ¿Derecho?

Mark Adler
fuente
8

Realmente depende del tipo de xya y de la implementación de +. por

   T x, a;
   ....
   x = x + a;

el compilador tiene que crear una T temporal para contener el valor de x + a mientras lo evalúa, que luego puede asignar a x. (No puede usar xo como espacio de trabajo durante esta operación).

Para x + = a, no necesita un temporal.

Para los tipos triviales, no hay diferencia.

Tom Tanner
fuente
8

La diferencia entre x = x + ay x += aes la cantidad de trabajo que debe realizar la máquina: algunos compiladores pueden (y generalmente lo hacen) optimizarla, pero generalmente, si ignoramos la optimización por un tiempo, lo que sucede es que en el fragmento de código anterior, el machine tiene que buscar el valor xdos veces, mientras que en el último, esta búsqueda debe ocurrir solo una vez.

Sin embargo, como mencioné, hoy en día la mayoría de los compiladores son lo suficientemente inteligentes como para analizar la instrucción y reducir las instrucciones de máquina resultantes requeridas.

PD: ¡Primera respuesta en Stack Overflow!

Sagar Ahire
fuente
6

Como ha etiquetado este C ++, no hay forma de saberlo por las dos declaraciones que ha publicado. Necesita saber qué es 'x' (es un poco como la respuesta '42'). Si xes un POD, entonces realmente no hará mucha diferencia. Sin embargo, si xes una clase, puede haber sobrecargas para los métodos operator +y operator +=que podrían tener diferentes comportamientos que conducen a tiempos de ejecución muy diferentes.

Skizz
fuente
6

Si dice que +=le está haciendo la vida mucho más fácil al compilador. Para que el compilador reconozca que x = x+aes lo mismo que x += a, el compilador debe

  • analice el lado izquierdo ( x) para asegurarse de que no tenga efectos secundarios y siempre se refiera al mismo valor l. Por ejemplo, podría serlo z[i], y debe asegurarse de que ambos zy ino cambien.

  • analice el lado derecho ( x+a) y asegúrese de que sea una suma, y ​​que el lado izquierdo aparezca una vez y solo una vez en el lado derecho, aunque podría transformarse, como en z[i] = a + *(z+2*0+i).

Si lo que quiere decir es agregar aa x, el escritor compilador lo aprecia cuando se acaba de decir lo que quiere decir. De esa manera, no estás ejercitando la parte del compilador de la que su escritor espera que haya eliminado todos los errores, y eso en realidad no te hace la vida más fácil, a menos que honestamente no puedas sacar la cabeza del modo Fortran.

Mike Dunlavey
fuente
5

Para un ejemplo concreto, imagine un tipo de número complejo simple:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

Para el caso a = a + b, tiene que hacer una variable temporal extra y luego copiarla.

Aleatorio832
fuente
este es un ejemplo muy bonito, muestra cómo se implementaron 2 operadores.
Grijesh Chauhan
5

Estás haciendo la pregunta incorrecta.

Es poco probable que esto impulse el rendimiento de una aplicación o función. Incluso si lo fuera, la forma de averiguarlo es perfilar el código y saber con certeza cómo te afecta. En lugar de preocuparse a este nivel por cuál es más rápido, es mucho más importante pensar en términos de claridad, corrección y legibilidad.

Esto es especialmente cierto cuando se considera que, incluso si se trata de un factor de rendimiento significativo, los compiladores evolucionan con el tiempo. Alguien puede encontrar una nueva optimización y la respuesta correcta hoy puede volverse incorrecta mañana. Es un caso clásico de optimización prematura.

Esto no quiere decir que el rendimiento no importe en absoluto ... Solo que es el enfoque incorrecto para lograr sus objetivos de rendimiento. El enfoque correcto es utilizar herramientas de creación de perfiles para saber dónde gasta realmente su tiempo su código y, por lo tanto, dónde centrar sus esfuerzos.

Joel Coehoorn
fuente
Concedido, pero fue pensado como una pregunta de bajo nivel, no como un panorama general "¿Cuándo debo considerar tal diferencia"?
Chiffa
1
La pregunta del OP era totalmente legítima, como lo demuestran las respuestas de otros (y votos a favor). Aunque entendemos su punto (perfil primero, etc.), definitivamente es interesante saber este tipo de cosas: ¿realmente va a perfilar todas y cada una de las declaraciones triviales que escriba, perfilar los resultados de cada decisión que tome? ¿Incluso cuando hay personas en SO que ya estudiaron, perfilaron, desmontaron el caso y tienen una respuesta general que dar?
4

Creo que eso debería depender de la máquina y su arquitectura. Si su arquitectura permite el direccionamiento indirecto de memoria, el escritor del compilador PODRÍA usar este código en su lugar (para la optimización):

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

Considerando que, i = i + ypodría traducirse a (sin optimización):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


Dicho esto, itambién se deben considerar otras complicaciones, como si es una función que devuelve el puntero, etc. La mayoría de los compiladores de nivel de producción, incluido GCC, producen el mismo código para ambas declaraciones (si son enteros).

Aniket Inge
fuente
2

No, ambas formas se manejan de la misma manera.

NubladoMármol
fuente
10
No si es un tipo definido por el usuario con operadores sobrecargados.