Tenemos la pregunta ¿hay una diferencia de rendimiento entre i++
y ++i
en C ?
¿Cuál es la respuesta para C ++?
c++
performance
oop
post-increment
pre-increment
Mark Harrison
fuente
fuente
Respuestas:
[Resumen ejecutivo: utilícelo
++i
si no tiene un motivo específico para utilizarloi++
]Para C ++, la respuesta es un poco más complicada.
Si
i
es un tipo simple (no una instancia de una clase C ++), entonces la respuesta dada para C ("No, no hay diferencia de rendimiento") es válida, ya que el compilador está generando el código.Sin embargo, si
i
es una instancia de una clase C ++, entoncesi++
y++i
está haciendo llamadas a una de lasoperator++
funciones. Aquí hay un par estándar de estas funciones:Como el compilador no genera código, sino que solo llama a una
operator++
función, no hay forma de optimizar latmp
variable y su constructor de copia asociado. Si el constructor de copias es costoso, esto puede tener un impacto significativo en el rendimiento.fuente
Si. Ahi esta.
El operador ++ puede o no definirse como una función. Para los tipos primitivos (int, double, ...) los operadores están integrados, 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 del operador ++ (), prefijo ++, no hay necesidad de crear una copia: el objeto puede incrementarse y luego simplemente devolverse.
Aquí hay una ilustración del punto:
Cada vez que llama al operador ++ (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 guarda una copia. Puede ser significativo en el caso de muchos incrementos (¿bucle grande?) Y / u objetos grandes.
fuente
C t(*this); ++(*this); return t;
En la segunda línea, estás incrementando este puntero correctamente, entonces, ¿cómot
se actualiza si estás incrementando esto? ¿No se copiaron ya los valores de estot
?The operator++(int) function must create a copy.
no, no es. No más copias queoperator++()
Aquí hay un punto de referencia para el caso cuando los operadores de incremento están en diferentes unidades de traducción. Compilador con g ++ 4.5.
Ignora los problemas de estilo por ahora
O (n) incremento
Prueba
Resultados
Resultados (los tiempos son en segundos) con g ++ 4.5 en una máquina virtual:
O (1) incremento
Prueba
Tomemos ahora el siguiente archivo:
No hace nada en el incremento. Esto simula el caso cuando el incremento tiene una complejidad constante.
Resultados
Los resultados ahora varían extremadamente:
Conclusión
En cuanto al rendimiento
Si no necesita el valor anterior, acostúmbrese a usar pre-incremento. 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 integrado con un tipo personalizado.
Semántico sabio
i++
diceincrement i, I am interested in the previous value, though
.++i
diceincrement i, I am interested in the current value
oincrement i, no interest in the previous value
. Una vez más, te acostumbrarás, incluso si no lo estás ahora.Knuth
La optimización prematura es la fuente de todos los males. Como es el pesimismo prematuro.
fuente
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
, no importa la estructura de árbol real (BSP, kd, Quadtree, Octree Grid, etc.). Tal iterador necesitaría mantener un estado, por ejemploparent node
,child node
,index
y cosas por el estilo. En general, mi postura es, incluso si existen pocos ejemplos, ...No es del todo correcto decir que el compilador no puede optimizar la copia variable temporal en el caso de postfix. 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 postfix, por ejemplo:
Ya sea que haga ++ testFoo o testFoo ++, seguirá obteniendo el mismo código resultante. De hecho, sin leer el recuento del usuario, el optimizador redujo todo a una constante. Así que esto:
Resultó en lo siguiente:
Entonces, si bien es cierto que la versión de 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.
fuente
La Guía de estilo de Google C ++ dice:
fuente
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 utilizamos la convención de ++ iter para obtener consistencia y rendimiento cuando corresponda. Pero Andrew plantea detalles pasados por alto con respecto a la intención frente al rendimiento. Hay momentos en los que queremos usar iter ++ en lugar de ++ iter.
Por lo tanto, primero decida su intención y si pre o post no importa, vaya con pre, ya que tendrá algún beneficio de rendimiento al evitar la creación de objetos adicionales y arrojarlos.
fuente
@Ketan
Obviamente, post y pre-incremento tienen semánticas diferentes 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é debería hacer uno cuando se descarta el resultado (como en los
for
bucles). La respuesta a esta 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 es más natural. Para mí++i
es más natural, pero mi experiencia me dice que soy minoritario y que el usoi++
causará menos gastos generales de metal para la mayoría personas que leen su código.Después de todo, esa es la razón por la cual el idioma no se llama "
++C
". [*][*] Insertar discusión obligatoria sobre
++C
ser un nombre más lógico.fuente
Cuando no se utiliza el valor de retorno, se garantiza que el compilador no utilizará un temporal en el caso de ++ i . No se garantiza que sea más rápido, pero se garantiza que no sea más lento.
Cuando se usa el valor de retorno, i ++ permite que el procesador empuje tanto el incremento como el lado izquierdo hacia la tubería, ya que no dependen el uno del otro. ++ Puedo detener la canalización porque el procesador no puede iniciar el lado izquierdo hasta que la operación de preincremento haya recorrido todo el camino. Una vez más, no se garantiza un bloqueo de la tubería, ya que el procesador puede encontrar otras cosas útiles para quedarse.
fuente
Mark: Solo quería señalar que los operadores ++ son buenos candidatos para estar en línea, y si el compilador decide hacerlo, la copia redundante se eliminará en la mayoría de los casos. (por ejemplo, tipos de POD, que suelen ser los iteradores).
Dicho esto, todavía es mejor usar ++ iter en la mayoría de los casos. :-)
fuente
La diferencia de rendimiento entre
++i
yi++
será más evidente cuando piense en los operadores como funciones de retorno de valor y cómo se implementan. Para facilitar la comprensión de lo que está sucediendo, los siguientes ejemplos de código se usaránint
como si fuera unstruct
.++i
incrementa la variable, luego devuelve el resultado. Esto se puede hacer en el lugar y con un tiempo mínimo de CPU, requiriendo solo una línea de código en muchos casos:Pero no se puede decir lo mismo
i++
.El incremento posterior
i++
, a menudo se considera que devuelve el valor original antes de incrementar. Sin embargo, una función solo puede devolver un resultado cuando está terminada . Como resultado, se hace necesario crear una copia de la variable que contiene el valor original, incrementar la variable y luego devolver la copia que contiene el valor original:Cuando no hay una diferencia funcional entre el pre-incremento y el post-incremento, el compilador puede realizar una optimización tal que no exista una diferencia de rendimiento entre los dos. Sin embargo, si se trata de un tipo de datos compuestos como a
struct
oclass
, se llamará al constructor de la copia en el post-incremento y no será posible realizar esta optimización si se necesita una copia profunda. Como tal, el incremento previo generalmente es más rápido y requiere menos memoria que el incremento posterior.fuente
@ Mark: eliminé mi respuesta anterior porque era un poco flip, y merecía un voto negativo solo por eso. De hecho, creo que es una buena pregunta en el sentido de que pregunta qué piensa mucha gente.
La respuesta habitual es que ++ i es más rápido que i ++, y sin duda lo es, pero la pregunta más importante es "¿cuándo debería importarle?"
Si la fracción del tiempo de CPU invertido en los iteradores incrementales es inferior al 10%, es posible que no le importe.
Si la fracción del tiempo de CPU invertido en incrementos de iteradores es mayor al 10%, puede ver qué declaraciones están haciendo esa iteración. Vea si podría simplemente incrementar los enteros en lugar de usar iteradores. Lo más probable es que pueda, y aunque en cierto sentido puede ser menos deseable, es muy probable que ahorre esencialmente todo el tiempo que pase en esos iteradores.
He visto un ejemplo en el que el incremento de iterador consumía más del 90% del tiempo. En ese caso, ir al incremento de números enteros redujo esencialmente el tiempo de ejecución en esa cantidad. (es decir, mejor que 10x de aceleración)
fuente
@wilhelmtell
El compilador puede eludir lo temporal. Verbatim del otro hilo:
El compilador de C ++ puede eliminar temporarios basados en la pila, incluso si al hacerlo cambia el comportamiento del programa. Enlace MSDN para VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
fuente
Y la razón por la que deberías usar ++ i incluso en tipos integrados donde no hay ventaja de rendimiento es crear un buen hábito para ti.
fuente
Ambos son tan rápidos;) Si lo desea, es el mismo cálculo para el procesador, es solo el orden en el que se hace lo que difiere.
Por ejemplo, el siguiente código:
Produzca el siguiente ensamblaje:
Verá que para a ++ y b ++ es un mnemónico incluido, por lo que es la misma operación;)
fuente
La pregunta que se pretendía era cuándo no se utiliza el resultado (eso queda claro de la pregunta para C). ¿Alguien puede arreglar esto ya que la pregunta es "wiki de la comunidad"?
Acerca de las optimizaciones prematuras, a menudo se cita a Knuth. Así es. pero Donald Knuth nunca defendería con ese código horrible que puedes ver en estos días. ¿Alguna vez has visto a = b + c entre enteros Java (no int)? Eso equivale a 3 conversiones de boxeo / unboxing. Evitar cosas como esas es importante. Y escribir inútilmente i ++ en lugar de ++ i es el mismo error. EDITAR: Como el phresnel lo pone muy bien en un comentario, esto se puede resumir como "la optimización prematura es mala, como lo es la pesimización prematura".
Incluso el hecho de que las personas estén más acostumbradas a i ++ es un desafortunado legado en C, causado por un error conceptual de K&R (si sigues el argumento de la intención, esa es una conclusión lógica; y defender K&R porque son K&R no tiene sentido, son genial, pero no son excelentes como diseñadores de lenguaje; existen innumerables errores en el diseño de C, que van desde gets () a strcpy (), a la API strncpy () (debería haber tenido la API strlcpy () desde el día 1) )
Por cierto, soy uno de los que no estoy lo suficientemente acostumbrado a C ++ para encontrar ++ i molesto para leer. Aún así, lo uso ya que reconozco que es correcto.
fuente
++i
más molesto quei++
(de hecho, lo encontré más fresco), pero el resto de tu publicación recibe mi reconocimiento completo. Tal vez agregue un punto "la optimización prematura es malvada, como lo es la pesimismo prematura"strncpy
cumplió un propósito en los sistemas de archivos que estaban usando en ese momento; el nombre del archivo era un búfer de 8 caracteres y no tenía que ser anulado. No se les puede culpar por no ver 40 años en el futuro de la evolución del lenguaje.strlcpy()
fue justificada por el hecho de que aún no se había inventado.Es hora de proporcionar gemas de sabiduría a las personas;): hay un simple truco para hacer que el incremento de postfix de C ++ se comporte casi igual que el incremento de prefijo (lo inventé para mí, pero también lo vi en el código de otras personas, así que no estoy solo).
Básicamente, el truco es usar la clase auxiliar para posponer el incremento después del regreso, y RAII viene a rescatar
Inventado es para algunos códigos de iteradores personalizados pesados, y reduce el tiempo de ejecución. El costo del prefijo vs postfix es una referencia ahora, y si este es un operador personalizado que se mueve mucho, el prefijo y el postfix me dieron el mismo tiempo de ejecución.
fuente
++i
es más rápido quei++
porque no devuelve una copia antigua del valor.También es más intuitivo:
Este ejemplo de C imprime "02" en lugar del "12" que podría esperar:
Lo mismo para C ++ :
fuente