2009-08-20 14 views
23

Mi compañero de trabajo afirma que para los tipos de objetos, la preincrementación es más eficiente que el incremento posterior¿Diferencia de rendimiento entre el iterador ++ y el iterador ++?

p.

std::vector<std::string> vec; 

... insert a whole bunch of strings into vec ... 

// iterate over and do stuff with vec. Is this more efficient than the next 
// loop? 
std::vector<std::string>::iterator it; 
for (it = vec.begin(); it != vec.end(); ++it){ 

} 

// iterate over and do stuff with vec. Is this less efficient than the previous loop? 
std::vector<std::string>::iterator it; 
for (it = vec.begin(); it != vec.end(); it++){ 

} 
+1

Bastante cerca de un duplicado de http://stackoverflow.com/questions/24901/is-there-a-performance-difference-between-i-and-i-in-c. Esta pregunta sí especifica iteradores, mientras que la pregunta vinculada es más general. – Eclipse

+0

Gracias ese enlace tiene una gran respuesta. – Matt

Respuesta

48

postincremento debe devolver el valor del iterador tenía antes de que fuera incrementando; entonces, ese valor anterior necesita ser copiado en algún lugar antes de alterarlo con el incremento apropiado, por lo que está disponible para regresar. El trabajo adicional puede ser un poco o mucho, pero ciertamente no puede ser menor que cero, en comparación con un preincremento, que simplemente puede realizar el incremento y luego devolver el valor recién modificado, sin copiar // guardar // etc. necesario.

Así que, a menos que específicamente DEBE tener postincrement (porque está usando el "valor antes del incremento" de alguna manera), siempre debe usar preincrement en su lugar. operadores de incremento

+0

Realmente no hay ninguna razón para que los iteradores estándar sean más lentos; ¿El compilador no puede asumir cosas en std :: behaves como especifica el estándar, y por lo tanto hace la misma optimización que hace para los tipos incorporados? – derobert

+2

derobert: Sí, si puede. No todos los tipos de iteradores son lo suficientemente simples para hacer ese tipo de optimización. Si el objeto es grande "suficiente" puede tener una ralentización. Podría decirse que casi nunca importará, pero uno usa este idioma en bucles apretados, donde realmente puede. – quark

+0

Tipo de una pregunta folklórica popular. Es bueno escuchar algunos detalles reales sobre él. –

5

Para los tipos primitivos, que utiliza para hacer esto. Solía ​​obtener un aumento del 10% en muchos programas en Visual Studio en 1998/1999. Poco después de eso, arreglaron su optimizador. El operador de incremento de post creó una variable en la pila, copió el valor, incrementó la fuente y luego eliminó la variable de la pila. Una vez que los optimizadores detectaron que no se estaba utilizando, el beneficio desapareció.

La mayoría de las personas no dejó de codificar de esa manera. :) Me tomó algunos años.

Para objetos, no estoy seguro de cómo funcionaría. Supongo que se requeriría un operador de copia para una implementación real del incremento posterior. Tendría que hacer una copia y usar la copia para las operaciones e incrementar la fuente.

Jacob

16

El defecto se vería así:

class X 
{ 
    X operator++(int) // Post increment 
    { 
     X tmp(*this); 
     this->operator++(); // Call pre-increment on this. 
     return tmp; 
    } 
    X& operator++() // Pre increment 
    { 
     // Do operation for increment. 
     return *this; 
    } 
}; 

Aviso al Post-incremento se define en términos de la pre-incremento. Así que el post-incremento hace el mismo trabajo más algunos adicionales. Por lo general, esto es construir una copia y luego devolver la copia mediante una copia (tenga en cuenta que el incremento previo puede devolverse por referencia, pero el incremento posterior no puede).

+1

tienes dos postfix allí. –

+0

¿Qué quieres decir con dos postfixes? Puedo ver publicaciones y publicaciones aquí. Incluso lo probé. – flyjohny

+0

@flyjohny: El comentario tiene un año de antigüedad y se refiere a la primera versión de la publicación antes de que la corrigiera: http://stackoverflow.com/posts/1304020/revisions –

3

En serio, a menos que tenga problemas de rendimiento debido a una llamada posterior al incremento en lugar del incremento previo LA DIFERENCIA DE RENDIMIENTO SÓLO IMPORTA EN CIRCUNSTANCIAS MUY RARAS.


actualización

Por ejemplo, si su software es una simulación del clima, y ​​operator++ hace que la simulación para avanzar un paso (es decir ++simulation le da el siguiente paso en la simulación, mientras que simulation++ realiza una copia del paso actual en la simulación, avanza la simulación y luego le entrega la copia), entonces el rendimiento será un problema.

De manera más realista, en un comentario, el interlocutor original menciona que estamos discutiendo un proyecto integrado con (aparentemente) requisitos de tiempo real. En ese caso, debe analizar su implementación específica.Dudo seriamente que tengas desaceleraciones notables iterando a través de un std::vector con un incremento posterior en lugar de un incremento previo, aunque no he comparado ninguna implementación de STL en este asunto.


No elija uno sobre el otro por motivos de rendimiento. Elija uno sobre el otro por razones de claridad/mantenimiento. Personalmente, prefiero preincrementarme porque, en general, me importa un comino cuál era el valor antes de incrementar.

Si su cerebro explota cuando ve ++i en lugar de i++ puede ser hora de considerar entrar en una línea de trabajo diferente.

+2

Correcto . Pero deberías preferir el preincremento. La razón es que cuando alguien cambia un tipo subyacente en el sistema, no es necesario volver atrás y verificar que todos los incrementos posteriores sean eficientes o no. Por lo tanto, para el mantenimiento, debe preferir el preincremento. –

+0

Todavía no tengo problemas de rendimiento, pero vale la pena pensar en el rendimiento de los sistemas en los que estoy trabajando. El software está operando en el sector de las telecomunicaciones atendiendo miles de llamadas por segundo. Pensé que simplemente preguntaría si hay una diferencia en el rendimiento y, obviamente, sí existe. – Matt

+0

Muy correcto.Pero la pregunta quería saber cuál es más eficiente solamente. – n002213f

0

Sé que esto es como "hangar-flying" en la aviación. Estoy seguro de que es puramente académico, y usted sabe si hace una gran diferencia que necesita volver a pensar su código.

Ejemplo: Tengo un comentario en alguna respuesta que he dado a alguna pregunta acerca de la optimización del rendimiento, y fue así:

persona: He intentado el método de detener al azar y mirando a la pila de llamadas. Lo hice varias veces, pero no tiene sentido. Todo el tiempo, es profundo en algún código de incremento de iterador. ¿De qué sirve eso?

Yo: ¡Eso es genial! Significa que casi todo su tiempo va a aumentar iteradores. Solo busca más arriba en la pila y mira de dónde viene. Reemplace eso con un simple incremento de enteros, y obtendrá un masivo speedup.

Por supuesto, es posible que prefiera la belleza del incremento de iteradores, pero es bastante fácil averiguar si le está costando mucho rendimiento.

+0

¿No hay perfiladores utilizables en su entorno que permitan obtener la misma información de una manera más sólida que la detención aleatoria de la pila? Los perfiladores de muestreo hacen esencialmente lo mismo, solo que con mucha más frecuencia y automáticamente. – Blaisorblade

+0

@Blaisorblade: echa un vistazo a la presentación de diapositivas en PDF * [aquí] (http://sourceforge.net/projects/randompausedemo/files/) *. Los perfiladores se basan en bajas expectativas. –

+0

Tengo que probarlo, pero tu idea tiene sentido. – Blaisorblade

4

He leído que no hay diferencia con los compiladores que tienen un buen optimizador. Como la lógica de bucle global es la misma para ambos, tiene sentido usar un incremento previo como un buen hábito de programación. De esta forma, si el programador encuentra el compilador "subóptimo", se asegura el mejor resultado.

Cuestiones relacionadas