c++ - intermedio - optimizacion de codigo en programacion
¿Debo optimizar o dejar que el compilador haga eso? (13)
Espero que el compilador optimice esto ...
No deberias Cualquier cosa que involucre
- Una llamada a una función desconocida o
- Una llamada a un método que podría ser anulado
Es difícil para un compilador de C ++ optimizar. Puede que tengas suerte, pero no puedes contar con ello.
Sin embargo, debido a que la primera versión es más simple y fácil de leer y entender, debe escribir el código exactamente como se muestra en su ejemplo simple , con las llamadas a size()
en el bucle. Debe considerar la segunda versión, donde tiene variables adicionales que sacan la llamada común del bucle, solo si su aplicación es demasiado lenta y si tiene mediciones que muestran que este bucle es un cuello de botella.
¿Cuál es el método preferido para escribir bucles de acuerdo con la eficiencia ?: a)
/*here I''m hoping that compiler will optimize this
code and won''t be calling size every time it iterates through this loop*/
for (unsigned i = firstString.size(); i < anotherString.size(), ++i)
{
//do something
}
o tal vez debería hacerlo de esta manera: Camino b)
unsigned first = firstString.size();
unsigned second = anotherString.size();
y ahora puedo escribir:
for (unsigned i = first; i < second, ++i)
{
//do something
}
La segunda forma me parece una opción peor por dos razones: el alcance de la contaminación y la verbosidad, pero tiene la ventaja de estar seguro de que el tamaño () se invocará una vez para cada objeto.
Mirando hacia adelante a sus respuestas.
¿Cuánto porcentaje de tiempo se gasta en
for
lugar de// do something
? (No adivine, pruébelo). Si es <10%, es probable que tenga problemas más grandes en otros lugares.Todo el mundo dice "Los compiladores son tan inteligentes en estos días". Bueno, no son más inteligentes que los codificadores pobres que los escriben. Necesitas ser inteligente también. Tal vez el compilador pueda optimizarlo, pero ¿por qué tentarlo a no hacerlo?
¿Cómo optimizará un buen compilador tu código? No, en absoluto, ya que no puede estar seguro de que el size()
tenga efectos secundarios. Si size()
tuviera algún efecto secundario en el que se basara su código, ahora desaparecería luego de una posible optimización del compilador.
Este tipo de optimización realmente no es seguro desde la perspectiva de un compilador, necesita hacerlo por su cuenta. Hacerlo por su cuenta no significa que deba introducir dos variables locales adicionales. Dependiendo de su implementación de tamaño, podría ser una operación O (1). Si el tamaño también se declara en línea, también ahorrará la llamada a la función, haciendo que la llamada a size()
tan buena como el acceso de un miembro local.
Así es como lo veo. El rendimiento y el estilo son importantes, y tienes que elegir entre los dos.
Puedes probarlo y ver si hay un impacto de rendimiento. Si hay un impacto de rendimiento inaceptable, elija la segunda opción, de lo contrario, no dude en elegir el estilo.
En general, dejar que el compilador lo haga. Concéntrese en la complejidad algorítmica de lo que está haciendo en lugar de micro-optimizaciones.
Sin embargo, tenga en cuenta que sus dos ejemplos no son semánticamente idénticos: si el cuerpo del bucle cambia el tamaño de la segunda cadena, los dos bucles no se repetirán la misma cantidad de veces. Por ese motivo, es posible que el compilador no pueda realizar la optimización específica de la que está hablando.
Esta es una de esas cosas que debes ponerte a prueba. Ejecute los bucles 10,000 o incluso 100,000 iteraciones y vea qué diferencia, si existe, existe.
Eso debería decirte todo lo que quieres saber.
Mi recomendación es dejar que optimizaciones intrascendentes se introduzcan en tu estilo. Lo que quiero decir con esto es que si aprendes una forma más óptima de hacer algo y no puedes ver ninguna desventaja (en lo que respecta a mantenibilidad, legibilidad, etc.), entonces es mejor que lo adoptes.
Pero no te obsesiones. Las optimizaciones que sacrifican la capacidad de mantenimiento deben guardarse para secciones muy pequeñas de código que haya medido y KNOW tendrá un gran impacto en su aplicación. Cuando decida optimizar, recuerde que elegir el algoritmo correcto para el trabajo es a menudo mucho más importante que el código estricto.
No debe optimizar su código, a menos que tenga una prueba (obtenida a través del generador de perfiles) de que esta parte del código es un cuello de botella. La optimización de código innecesaria solo desperdiciará su tiempo, no mejorará nada.
Puede perder horas tratando de mejorar un bucle, solo para obtener un aumento de rendimiento de 0.001%. Si te preocupa el rendimiento, usa perfiladores.
No hay nada realmente malo con la forma (b) si solo quieres escribir algo que probablemente no sea peor que la forma (a), y posiblemente más rápido. También deja claro que sabes que el tamaño de la cadena permanecerá constante.
El compilador puede o no detectar que el size
permanecerá constante; Por si acaso, también podría realizar esta optimización usted mismo. Ciertamente haría esto si sospechara que el código que estaba escribiendo iba a correr mucho, incluso si no estuviera seguro de que sería un gran problema. Es muy sencillo de hacer, no se tarda más de 10 segundos adicionales en pensarlo, es muy poco probable que ralentice las cosas y, como mínimo, hará que la versión no optimizada se ejecute un poco más rápido.
(Además, la first
variable en el estilo (b) no es necesaria; el código para la expresión de inicio se ejecuta solo una vez).
Normalmente escribo este código como:
/* i and size are local to the loop */
for (size_t i = firstString.size(), size = anotherString.size(); i < size; ++i) {
//do something
}
de esta manera no contamino el ámbito principal y evito llamar a anotherString.size()
para cada iteración de bucle.
Esto es especialmente útil para los iteradores:
for(some_generic_type<T>::forward_iterator it = collection.begin(), end = collection.end();
it != end; ++it) {
// do something with *it
}
Para la función miembro "std :: size_t size () const", que no solo es O (1) sino que también se declara "const" y, por lo tanto, el compilador puede sacarla automáticamente del bucle, probablemente no importa. Dicho esto, no contaría con que el compilador lo elimine del bucle, y creo que es un buen hábito entrar a factorizar las llamadas dentro del bucle para los casos en que la función no es constante o O (1 ). Además, creo que asignar los valores a una variable hace que el código sea más legible. Sin embargo, no sugeriría que realice optimizaciones prematuras si el código resulta más difícil de leer. Una vez más, sin embargo, creo que el siguiente código es más legible, ya que hay menos para leer dentro del bucle:
std::size_t firststrlen = firststr.size();
std::size_t secondstrlen = secondstr.size();
for ( std::size_t i = firststrlen; i < secondstrlen; i++ ){
// ...
}
Además, debo señalar que debe usar "std :: size_t" en lugar de "unsigned", ya que el tipo de "std :: size_t" puede variar de una plataforma a otra, y el uso de "unsigned" puede llevar a trunctaciones y errores en plataformas para las que el tipo de "std :: size_t" es "unsigned long" en lugar de "unsigned int".
Primero usaría la primera versión, simplemente porque se ve más limpia y más fácil de escribir. Luego puede crear un perfil para ver si algo necesita optimizarse.
Pero dudo mucho que la primera versión cause una caída notable en el rendimiento. Si el contenedor implementa size()
esta manera:
inline size_t size() const
{
return _internal_data_member_representing_size;
}
entonces el compilador debe ser capaz de alinear la función, eliminando la llamada a la función. La implementación de los contenedores estándar por parte de mi compilador hace esto.
No pre-optimice su código. Si tiene un problema de rendimiento, use un generador de perfiles para encontrarlo, de lo contrario, está perdiendo el tiempo de desarrollo. Solo escribe el código más simple / limpio que puedas.