Tenemos la pregunta ¿hay una diferencia de rendimiento entre i++
y ++i
C ª?
¿Cuál es la respuesta para C++?
[*]
marca harrison
Tenemos la pregunta ¿hay una diferencia de rendimiento entre i++
y ++i
C ª?
¿Cuál es la respuesta para C++?
[*]
marca harrison
[Executive Summary: Use ++i
if you don’t have a specific reason to use i++
.]
Para C++, la respuesta es un poco más complicada.
Si i
es un tipo simple (no una instancia de una clase de C++), entonces la respuesta dada para C (“No, no hay diferencia de rendimiento”) se mantiene, ya que el compilador está generando el código.
Sin embargo, si i
es una instancia de una clase de C++, entonces i++
y ++i
están haciendo llamadas a uno de los operator++
funciones Aquí hay un par estándar de estas funciones:
Foo& Foo::operator++() // called for ++i
{
this->data += 1;
return *this;
}
Foo Foo::operator++(int ignored_dummy_value) // called for i++
{
Foo tmp(*this); // variable "tmp" cannot be optimized away by the compiler
++(*this);
return tmp;
}
Dado que el compilador no está generando código, sino simplemente llamando a un operator++
función, no hay forma de optimizar la distancia tmp
variable y su constructor de copia asociado. Si el constructor de copias es costoso, esto puede tener un impacto significativo en el rendimiento.
Lo que el compilador puede evitar es que la segunda copia devuelva tmp, asignando tmp en la persona que llama, a través de NRVO, como se menciona en otro comentario.
– Blaisorblade
15 de enero de 2009 a las 0:13
¿No puede el compilador evitar esto por completo si el operador ++ está en línea?
– Eduard-Gabriel Munteanu
18 de febrero de 2009 a las 15:59
Sí, si operator++ está en línea y tmp nunca se usa, se puede eliminar a menos que el constructor o destructor del objeto tmp tenga efectos secundarios.
– Zan Lince
9 de septiembre de 2009 a las 1:21
@kriss: la diferencia entre C y C ++ es que en C tiene la garantía de que el operador estará en línea, y en ese momento un optimizador decente podrá eliminar la diferencia; en cambio, en C ++ no puede asumir la inserción, no siempre.
– Blaisorblade
5 de marzo de 2012 a las 10:45
Haría +1 SI la respuesta mencionara algo sobre las clases que contienen punteros (ya sea automático, inteligente o primitivo) a la memoria asignada dinámicamente (montón), donde el constructor de copia necesariamente realiza copias profundas. En tales casos, no hay argumento, ++i es quizás un orden de magnitud más eficiente que i++. La clave es adquirir el hábito de usar pre-incremento siempre que su algoritmo no requiera semántica de post-incremento, y entonces tendrá el hábito de escribir código que por naturaleza se presta a una mayor eficiencia, independientemente de cómo así su compilador puede optimizar.
– etiquetador telefónico
17 de julio de 2012 a las 18:57
guillermo
Si. Hay.
El operador ++ puede o no estar definido como una función. Para los tipos primitivos (int, double, …) los operadores están incorporados, por lo que el compilador probablemente podrá optimizar su código. Pero en el caso de un objeto que define el operador ++, las cosas son diferentes.
La función operator++(int) debe crear una copia. Esto se debe a que se espera que postfix ++ devuelva un valor diferente al que contiene: debe mantener su valor en una variable temporal, incrementar su valor y devolver la temperatura. En el caso de operator++(), prefijo ++, no hay necesidad de crear una copia: el objeto puede incrementarse y luego simplemente regresar a sí mismo.
Aquí hay una ilustración del punto:
struct C
{
C& operator++(); // prefix
C operator++(int); // postfix
private:
int i_;
};
C& C::operator++()
{
++i_;
return *this; // self, no copy created
}
C C::operator++(int ignored_dummy_value)
{
C t(*this);
++(*this);
return t; // return a copy
}
Cada vez que llama al operator++(int) debe crear una copia y el compilador no puede hacer nada al respecto. Cuando se le dé la opción, use operator++(); de esta manera no guardas una copia. Puede ser significativo en el caso de muchos incrementos (¿bucle grande?) y/u objetos grandes.
“El operador de preincremento introduce una dependencia de datos en el código: la CPU debe esperar a que se complete la operación de incremento antes de que su valor pueda usarse en la expresión. En una CPU profundamente canalizada, esto introduce un bloqueo. No hay dependencia de datos para el operador de incremento posterior”. (Arquitectura del motor de juego (2ª edición)) Entonces, si la copia de un incremento posterior no es computacionalmente intensivo, aún puede superar el incremento previo.
– Matías
9 oct 2017 a las 18:18
En el código postfijo, ¿cómo funciona esto? C t(*this); ++(*this); return t;
En la segunda línea, está incrementando este puntero a la derecha, entonces, ¿cómo t
Actualícese si está incrementando esto. ¿No fueron los valores de esto ya copiados en t
?
– rasen58
11 de noviembre de 2017 a las 3:37
The operator++(int) function must create a copy.
no, no es. No más copias que operator++()
– Severin Pappadeux
7 abr 2019 a las 22:29
Aquí hay un punto de referencia para el caso en que los operadores de incremento están en diferentes unidades de traducción. Compilador con g++ 4.5.
Ignora los problemas de estilo por ahora.
// a.cc
#include <ctime>
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
int main () {
Something s;
for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
std::clock_t a = clock();
for (int i=0; i<1024*1024*30; ++i) ++s;
a = clock() - a;
for (int i=0; i<1024*1024*30; ++i) s++; // warm up
std::clock_t b = clock();
for (int i=0; i<1024*1024*30; ++i) s++;
b = clock() - b;
std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
<< ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
return 0;
}
// b.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
for (auto it=data.begin(), end=data.end(); it!=end; ++it)
++*it;
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
Resultados (los tiempos son en segundos) con g ++ 4.5 en una máquina virtual:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 1.70 2.39
-DPACKET_SIZE=50 -O3 0.59 1.00
-DPACKET_SIZE=500 -O1 10.51 13.28
-DPACKET_SIZE=500 -O3 4.28 6.82
Tomemos ahora el siguiente archivo:
// c.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
No hace nada en el incremento. Esto simula el caso cuando la incrementación tiene una complejidad constante.
Los resultados ahora varían extremadamente:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 0.05 0.74
-DPACKET_SIZE=50 -O3 0.08 0.97
-DPACKET_SIZE=500 -O1 0.05 2.79
-DPACKET_SIZE=500 -O3 0.08 2.18
-DPACKET_SIZE=5000 -O3 0.07 21.90
Si no necesita el valor anterior, acostúmbrese a usar el preincremento. Sea coherente incluso con los tipos incorporados, se acostumbrará y no correrá el riesgo de sufrir una pérdida de rendimiento innecesaria si alguna vez reemplaza un tipo incorporado con un tipo personalizado.
i++
dice increment i, I am interested in the previous value, though
.++i
dice increment i, I am interested in the current value
o increment i, no interest in the previous value
. Nuevamente, te acostumbrarás, incluso si no lo estás ahora.La optimización prematura es la fuente de todos los males. Como lo es la pesimización prematura.
Prueba interesante. Ahora, casi dos años y medio después, gcc 4.9 y Clang 3.4 muestran una tendencia similar. Clang es un poco más rápido con ambos, pero la disparidad entre pre y postfix es peor que gcc.
– masticar calcetines
14 de agosto de 2014 a las 15:06
Lo que realmente me gustaría ver es un ejemplo del mundo real donde ++i / i++ marque la diferencia. Por ejemplo, ¿hace alguna diferencia en alguno de los iteradores estándar?
–Jakob Schou Jensen
4 de junio de 2015 a las 6:58
@JakobSchouJensen: Estos tenían la intención de ser ejemplos del mundo real. Considere una aplicación grande, con estructuras de árbol complejas (p. ej., kd-trees, quad-trees) o contenedores grandes utilizados en plantillas de expresión (para maximizar el rendimiento de datos en el hardware SIMD). Si hace una diferencia allí, no estoy realmente seguro de por qué uno recurriría al incremento posterior para casos específicos si eso no es necesario desde el punto de vista semántico.
– Sebastián Mach
5 de junio de 2015 a las 8:31
@phresnel: No creo que operator++ esté en su plantilla de expresión diaria. ¿Tiene un ejemplo real de esto? El uso típico de operator++ es en enteros e iteradores. Ahí es donde creo que sería interesante saber si hay alguna diferencia (no hay diferencia en los números enteros, por supuesto, sino en los iteradores).
–Jakob Schou Jensen
6 jun 2015 a las 15:35
@JakobSchouJensen: No hay un ejemplo comercial real, pero sí algunas aplicaciones de procesamiento de números en las que cuenta cosas. Wrt iteradores, considere un trazador de rayos que está escrito en estilo idiomático C ++, y tiene un iterador para el recorrido primero en profundidad, de modo que for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
, no importa la estructura del árbol real (BSP, kd, Quadtree, Octree Grid, etc.). Tal iterador necesitaría mantener algún estado, por ejemplo parent node
, child node
, index
Y cosas como esa. Con todo, mi postura es, incluso si existen pocos ejemplos, …
– Sebastián Mach
7 junio 2015 a las 20:16
No es del todo correcto decir que el compilador no puede optimizar la copia de la variable temporal en el caso del sufijo. Una prueba rápida con VC muestra que, al menos, puede hacer eso en ciertos casos.
En el siguiente ejemplo, el código generado es idéntico para el prefijo y el sufijo, por ejemplo:
#include <stdio.h>
class Foo
{
public:
Foo() { myData=0; }
Foo(const Foo &rhs) { myData=rhs.myData; }
const Foo& operator++()
{
this->myData++;
return *this;
}
const Foo operator++(int)
{
Foo tmp(*this);
this->myData++;
return tmp;
}
int GetData() { return myData; }
private:
int myData;
};
int main(int argc, char* argv[])
{
Foo testFoo;
int count;
printf("Enter loop count: ");
scanf("%d", &count);
for(int i=0; i<count; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
}
Ya sea que haga ++testFoo o testFoo++, obtendrá el mismo código resultante. De hecho, sin leer el conteo del usuario, el optimizador redujo todo a una constante. Así que esto:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
Resultó en lo siguiente:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
Entonces, si bien es cierto que la versión postfix podría ser más lenta, es posible que el optimizador sea lo suficientemente bueno como para deshacerse de la copia temporal si no la está usando.
martjno
los Guía de estilo de Google C++ dice:
Preincremento y Predecremento
Use la forma de prefijo (++i) de los operadores de incremento y decremento con iteradores y otros objetos de plantilla.
Definición: Cuando una variable se incrementa (++i o i++) o se decrementa (–i o i–) y no se utiliza el valor de la expresión, se debe decidir si se preincrementa (decrementa) o se posincrementa (decrementa).
Ventajas: Cuando se ignora el valor de retorno, la forma “pre” (++i) nunca es menos eficiente que la forma “post” (i++) y suele ser más eficiente. Esto se debe a que el posincremento (o decremento) requiere que se haga una copia de i, que es el valor de la expresión. Si i es un iterador u otro tipo no escalar, copiar i podría ser costoso. Dado que los dos tipos de incremento se comportan de la misma manera cuando se ignora el valor, ¿por qué no realizar siempre un incremento previo?
Contras: Se desarrolló la tradición, en C, de usar post-incremento cuando no se usa el valor de la expresión, especialmente en bucles for. Algunos encuentran que el incremento posterior es más fácil de leer, ya que el “sujeto” (i) precede al “verbo” (++), al igual que en inglés.
Decisión: Para valores escalares simples (no objetos) no hay razón para preferir una forma y permitimos cualquiera de las dos. Para iteradores y otros tipos de plantillas, use preincremento.
“Decisión: para valores escalares simples (no objetos) no hay razón para preferir una forma y permitimos cualquiera de las dos. Para iteradores y otros tipos de plantillas, use el incremento previo”.
– Nosredna
28 de noviembre de 2009 a las 17:41
Eh,…, y que es ese algo?
– Sebastián Mach
15 de noviembre de 2014 a las 10:06
El enlace mencionado en la respuesta está actualmente roto.
– Karol
12 de julio de 2019 a las 7:12
Me gustaría señalar una excelente publicación de Andrew Koenig en Code Talk muy recientemente.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
En nuestra empresa también usamos la convención de ++iter para mantener la coherencia y el rendimiento cuando corresponde. Pero Andrew plantea detalles pasados por alto con respecto a la intención frente al rendimiento. Hay ocasiones en las que queremos usar iter++ en lugar de ++iter.
Entonces, primero decida su intención y si pre o post no importa, entonces vaya con pre, ya que tendrá algún beneficio de rendimiento al evitar la creación de objetos adicionales y tirarlos.
“Decisión: para valores escalares simples (no objetos) no hay razón para preferir una forma y permitimos cualquiera de las dos. Para iteradores y otros tipos de plantillas, use el incremento previo”.
– Nosredna
28 de noviembre de 2009 a las 17:41
Eh,…, y que es ese algo?
– Sebastián Mach
15 de noviembre de 2014 a las 10:06
El enlace mencionado en la respuesta está actualmente roto.
– Karol
12 de julio de 2019 a las 7:12
@Ketan
… plantea detalles pasados por alto con respecto a la intención frente al rendimiento. Hay ocasiones en las que queremos usar iter++ en lugar de ++iter.
Obviamente, la publicación y el preincremento tienen una semántica diferente y estoy seguro de que todos están de acuerdo en que cuando se usa el resultado, debe usar el operador apropiado. Creo que la pregunta es qué se debe hacer cuando se descarta el resultado (como en for
bucles). la respuesta a esta La pregunta (en mi humilde opinión) es que, dado que las consideraciones de rendimiento son insignificantes en el mejor de los casos, debe hacer lo que sea más natural. Para mí ++i
es mas natural pero mi experiencia me dice que soy minoria y uso i++
causará menos sobrecarga de metal para más personas que leen su código.
Después de todo, esa es la razón por la que el idioma no se llama “++C
“.[*]
[*] Insertar discusión obligatoria sobre ++C
siendo un nombre más lógico.
@Motti: (bromeando) El nombre de C++ es lógico si recuerda que Bjarne Stroustrup C++ lo codificó inicialmente como un precompilador que genera un programa en C. Por lo tanto, C++ devolvió un valor antiguo de C. O puede ser para resaltar que C ++ tiene algunas fallas conceptuales desde el principio.
– kriss
31 de marzo de 2010 a las 11:37
Reetiqueté ya que esas dos etiquetas son la forma más fácil de encontrar preguntas de esta naturaleza. También revisé otros que no tenían etiquetas cohesivas y les di etiquetas cohesivas.
– Jorge Stocker
10 de septiembre de 2009 a las 15:25
¿Hay alguna diferencia de rendimiento entre usar C++ y ++C?
– nuevo123456
26 de junio de 2011 a las 1:53
Artículo: ¿Es razonable usar el operador de incremento de prefijo ++it en lugar del operador de postfijo it++ para los iteradores? – viva64.com/es/b/0093
– usuario965097
16/04/2015 a las 14:35
Puede depender del procesador. El PDP-11 tenía modos de direccionamiento post-incremento y pre-decremento, por lo que
i++
y--i
eran más eficientes que++i
yi--
.– usuario207421
20 oct 2021 a las 23:27