c++ - ¿Debemos seguir optimizando "en lo pequeño"?
optimization (22)
Estaba cambiando mi bucle for para aumentar usando ++i
lugar de i++
y pensé, ¿es esto realmente necesario? Seguramente los compiladores de hoy hacen esta optimización por su cuenta.
En este artículo, http://leto.net/docs/C-optimization.php , desde 1997, Michael Lee ingresa en otras optimizaciones, tales como alineación, desenrollado de bucle, atasco de bucle, inversión de bucle, reducción de fuerza y muchos otros. ¿Siguen siendo relevantes?
¿Qué optimizaciones de código de bajo nivel deberíamos estar haciendo y qué optimizaciones podemos ignorar de manera segura?
Edición: Esto no tiene nada que ver con la optimización prematura. La decisión de optimizar ya se ha tomado. Ahora la pregunta es cuál es la forma más efectiva de hacerlo.
anécdota: una vez revisé una especificación de requisitos que decía: "El programador dejará el turno en uno en lugar de multiplicarlo por 2".
Ciertamente sí, porque el compilador necesita más recursos para optimizar el código no optimizado que para optimizar algo que ya está optimizado. En particular, hace que la computadora consuma un poco más de energía, que, a pesar de ser pequeña, todavía causa un gran impacto en la naturaleza ya dañada. Esto es especialmente importante para el código de código abierto, que se compila con mayor frecuencia que el código cerrado.
Ir verde, salvar el planeta, optimizarte
Claro, si y solo si resulta en una mejora real para ese programa en particular que es lo suficientemente importante como para que valga la pena el tiempo de codificación, la disminución de la legibilidad, etc. No creo que pueda establecer una regla para esto en todos los programas, o Realmente para cualquier optimización. Es totalmente dependiente de lo que realmente importa en el caso particular.
Con algo como ++ i, el intercambio de tiempo y legibilidad es tan pequeño, que puede valer la pena hacer un hábito, si en realidad resulta en una mejora.
Como han dicho otros, ++ i puede ser más eficiente que i ++ si i es una instancia de algún objeto. Esa diferencia puede o no ser significativa para usted.
Sin embargo, en el contexto de su pregunta sobre si el compilador puede hacer estas optimizaciones por usted, en el ejemplo que eligió no puede. La razón es que ++ i y i ++ tienen significados diferentes, por lo que se implementan como funciones diferentes. i ++ tiene que hacer un trabajo adicional (para copiar el estado actual antes de incrementar, hacer el incremento y luego devolver ese estado). Si no necesita ese trabajo adicional, ¿por qué elegir esa forma otra forma más directa? La respuesta podría ser legibilidad, pero en C ++ se ha vuelto idiomático al escribir ++ i en este caso, por lo que no creo que la legibilidad se incluya en ella.
Por lo tanto, dada la opción entre escribir un código que realice un trabajo adicional innecesario (que puede o no ser significativo), sin ningún beneficio en sí mismo, siempre elegiría la forma más directa. Esto no es una optimización prematura. Por otro lado, por lo general no es lo suficientemente significativo como para ser religioso.
El compilador está en una mejor posición para juzgar y tomar tales decisiones. Las micro optimizaciones que realice pueden interponerse en su camino y, en última instancia, perder el sentido.
En general, no. Los compiladores son mucho mejores para hacer micro-optimizaciones pequeñas y directas como esta en toda su base de código. Asegúrese de que está habilitando su compilador aquí, compilando su versión de lanzamiento con los indicadores de optimización correctos. Si usa Visual Studio, podría querer experimentar con el favorecimiento del tamaño sobre la velocidad (hay muchos casos donde el código pequeño es más rápido), la generación de código de tiempo de enlace (LTCG, que permite al compilador realizar optimizaciones de compilaciones cruzadas), y tal vez incluso la optimización guiada por perfil.
También debe recordar que la mayor parte de su código no importará desde una perspectiva de rendimiento: la optimización de este código no tendrá ningún efecto visible para el usuario.
Debe definir sus objetivos de rendimiento desde el principio y medirlos con frecuencia para asegurarse de que los cumple. Cuando esté fuera de sus objetivos, use herramientas como los perfiladores para determinar dónde están los puntos calientes en su código y optimícelos.
Como se mencionó en otro póster here , "la optimización sin medir y comprender no es en absoluto una optimización, es solo un cambio aleatorio".
Si ha medido y determinado que una función o un bucle en particular es un punto de acceso, hay dos enfoques para optimizarlo:
- Primero, optimícelo a un nivel superior reduciendo las invocaciones del código costoso. Esto generalmente llevará a la mayoría de los beneficios. Las mejoras en el nivel de algoritmo caen en este nivel: un algoritmo con una gran O mejor resultará en que se ejecute menos el código del punto de acceso.
- Si las llamadas no se pueden reducir, debería considerar micro-optimizaciones. Mire el código de máquina real que está emitiendo el compilador y determine qué es lo que está haciendo que es el más costoso; si resulta que está ocurriendo la copia de objetos temporales, entonces considere el prefijo ++ sobre el postfix. Si está haciendo una comparación innecesaria al principio del bucle, invierta el bucle en un hacer / tiempo, y así sucesivamente. Sin entender por qué el código es lento, cualquier microoptimización general es casi inútil.
Esas optimizaciones siguen siendo relevantes. Con respecto a su ejemplo, el uso de ++ i o i ++ en un tipo aritmético incorporado no tiene ningún efecto.
En el caso de operadores de incremento / decremento definidos por el usuario, es preferible ++ i, porque no implica copiar el objeto incrementado.
Por lo tanto, un buen estilo de codificación es utilizar el incremento / decremento de prefijo en los bucles.
Este es un tema muy usado, y SO contiene montones de buenos y malos consejos al respecto.
Déjame que te cuente lo que he encontrado de mucha experiencia en la optimización del rendimiento.
Hay curvas de compensación entre el rendimiento y otras cosas como la memoria y la claridad, ¿verdad? Y esperaría que para obtener un mejor rendimiento tendría que renunciar a algo, ¿verdad?
Eso solo es cierto si el programa está en la curva de compensación . La mayoría del software, como se escribió por primera vez, está a millas de distancia de la curva de compensación . La mayoría de las veces, es irrelevante e ignorante hablar de renunciar a una cosa para obtener otra.
El método que utilizo no es la medición, es el diagnosis . No me importa qué tan rápido son las distintas rutinas o con qué frecuencia se llaman. Quiero saber con precisión qué instrucciones están causando lentitud y por qué .
La causa dominante y principal del mal desempeño en los esfuerzos de software de buen tamaño (no en los proyectos de una sola persona) es la generalidad galopante . Se emplean demasiados niveles de abstracción, cada uno de los cuales extrae una penalización de rendimiento. Esto no suele ser un problema, hasta que es un problema, y luego es un asesino.
Entonces, lo que hago es abordar un problema a la vez. Yo los llamo "babosas", abreviatura de "errores de lentitud". Cada bala que quito produce una aceleración de 1.1x a 10x, dependiendo de lo malo que sea. Cada bala que se elimina hace que las restantes tomen una fracción mayor del tiempo restante, de modo que sean más fáciles de encontrar. De esta manera, todas las "frutas colgantes" se pueden eliminar rápidamente.
En ese momento, sé lo que cuesta el tiempo, pero las correcciones pueden ser más difíciles, como el rediseño parcial del software, posiblemente eliminando una estructura de datos extraños o utilizando la generación de código. Si es posible hacerlo, esto puede desencadenar una nueva ronda de eliminación de babosas hasta que el programa sea muchas veces más rápido de lo que era antes, pero también más pequeño y claro.
Recomiendo adquirir una experiencia como esta para ti mismo, porque cuando diseñes un software sabrás qué no hacer y, para empezar, harás diseños mejores (y más simples). Al mismo tiempo, se encontrará en desacuerdo con colegas menos experimentados que no pueden comenzar a pensar en un diseño sin evocar una docena de clases.
AGREGADO: ahora, para tratar de responder a su pregunta, se debe realizar una optimización de bajo nivel cuando el diagnóstico indique que tiene un punto caliente (es decir, aparece algún código en la parte inferior de la pila de llamadas en suficientes muestras de pila de llamadas (10% o más) que se sabe que cuesta mucho tiempo). Y si el punto de acceso está en código, puede editarlo. Si tienes un punto caliente en "nuevo", "eliminar", o comparar cadenas, mira más arriba en la pila de cosas para deshacerte.
Espero que ayude.
Haga lo correcto, luego hágalo rápido, basado en la medición del desempeño.
Elija bien los algoritmos e impleméntelos de la manera MÁS LECTABLE posible. Cambie la legibilidad por el rendimiento solo cuando DEBE, es decir, cuando su usuario dice que el rendimiento es inaceptable, ya sea por palabras o por sus acciones.
Como dijo Donald Knuth / Tony Hoare, "la optimización prematura es la raíz de todo mal", sigue siendo verdad ahora 30 años después ...
Hay tres citas que creo que todos los desarrolladores deberían saber con respecto a la optimización. Primero las leí en el libro "Effective Java" de Josh Bloch:
Se cometen más pecados informáticos en nombre de la eficiencia (sin necesariamente lograrlo) que por cualquier otra razón, incluida la estupidez ciega.
( William A. Wulf )
Debemos olvidarnos de las pequeñas eficiencias, digamos que aproximadamente el 97% de las veces: la optimización prematura es la raíz de todo mal.
( Donald E. Knuth )
Seguimos dos reglas en materia de optimización:
Regla 1: No lo hagas.
Regla 2: (sólo para expertos). No lo haga todavía, es decir, no hasta que tenga una solución perfectamente clara y sin optimizar.
( MA Jackson )
Todas estas citas tienen (AFAIK) al menos 20-30 años, un momento en el que la CPU y la memoria significan mucho más que hoy. Creo que la forma correcta de desarrollar software es primero tener una solución que funcione, y luego usar un generador de perfiles para probar dónde están los cuellos de botella en el rendimiento. Una vez, un amigo me contó sobre una aplicación que estaba escrita en C ++ y Delphi y tenía problemas de rendimiento. Usando un generador de perfiles descubrieron que la aplicación dedicó una cantidad considerable de tiempo a convertir las cadenas de la estructura de Delphi a la de C ++ y viceversa; ninguna optimización micro puede detectar eso ...
Para concluir, no piense que sabe dónde estarán los problemas de rendimiento. Utilice un perfilador para esto.
La última vez que probé ++ it and it ++ en el compilador Microsoft C ++ para iteradores STL, ++ emitió menos código, por lo que si está en un bucle masivo puede obtener una pequeña ganancia de rendimiento con ++ it.
Para enteros, etc., el compilador emitirá un código idéntico.
Mal ejemplo: la decisión de usar ++i
o i++
no implica ningún tipo de compensación. ++i
tiene (puede tener) un beneficio neto sin inconvenientes . Hay muchos escenarios similares y cualquier discusión en estos reinos es una pérdida de tiempo.
Dicho esto, creo que es muy importante saber hasta qué punto el compilador de destino es capaz de optimizar pequeños fragmentos de código . La verdad es que los compiladores modernos son (a veces sorprendentemente!) Buenos en eso. Jason tiene una historia increíble sobre una función factorial optimizada (no recursiva de cola).
Por otro lado, los compiladores también pueden ser sorprendentemente estúpidos. La clave es que muchas optimizaciones requieren un análisis de flujo de control que se convierte en NP completo. La optimización siempre se convierte así en una compensación entre el tiempo de compilación y la utilidad. A menudo, la localidad de una optimización juega un papel crucial porque el tiempo de cálculo requerido para realizar la optimización aumenta demasiado cuando el tamaño del código considerado por el compilador aumenta en solo unas pocas declaraciones.
Y como han dicho otros, estos detalles minuciosos siguen siendo relevantes y siempre lo serán (para el futuro previsible). Aunque los compiladores se vuelven más inteligentes todo el tiempo y las máquinas se vuelven más rápidas, también aumenta el tamaño de nuestros datos; de hecho, estamos perdiendo esta batalla en particular; En muchos campos, la cantidad de datos crece mucho más rápido de lo que mejoran las computadoras.
No intentes adivinar qué está haciendo tu compilador. Si ya ha determinado que necesita optimizar algo en este nivel, aísle ese bit y observe el ensamblaje generado. Si puede ver que el código generado está haciendo algo lento que puede mejorarse, por supuesto, juegue con él a nivel de código y vea qué sucede. Si realmente necesita control, vuelva a escribir ese bit en el ensamblaje y vincúlelo.
Esto es un dolor en el culo, pero la única manera de ver realmente lo que está sucediendo. Tenga en cuenta que todas estas optimizaciones ajustadas pueden volverse inútiles una vez que cambie algo (CPU diferente, compilador diferente, incluso caché diferente, etc.) y es un costo irrecuperable.
Quería añadir un poco de algo. Esta "optimización prematura es mala" es una especie de basura. ¿Qué haces cuando seleccionas un algoritmo? Probablemente tome el que tenga la mejor complejidad de tiempo: la optimización prematura de OMG. Sin embargo, todo el mundo parece estar bien con esto. Así que parece que la verdadera actitud es "la optimización prematura es mala, a menos que lo hagas a mi manera" Al final del día, haz lo que sea necesario para crear la aplicación que necesitas crear.
"El programador deberá desplazar a la izquierda por uno en lugar de multiplicarse por 2". Espero que no quieras multiplicar flotadores o números negativos entonces;)
Sí, esas cosas siguen siendo relevantes. Hago un poco de ese tipo de optimización pero, para ser justos, escribo sobre todo código que tiene que hacer cosas relativamente complejas en unos 10 ms en un ARM9. Si está escribiendo un código que se ejecuta en CPU más modernas, los beneficios no serán tan grandes.
Si no le importa la portabilidad y está haciendo un poco de matemáticas, entonces también podría considerar el uso de las operaciones vectoriales disponibles en su plataforma de destino: SSE en x86, Altivec en PPC. Los compiladores no pueden usar estas instrucciones fácilmente sin mucha ayuda, y los intrínsecos son bastante fáciles de usar en estos días. Otra cosa que no se menciona en el documento al que está vinculado es el alias de punteros. A veces puede obtener mejoras en la velocidad si su compilador tiene soporte para algún tipo de palabra clave "restringir". Además, por supuesto, pensar en el uso del caché puede ser importante. Reorganizar su código y sus datos de una manera que haga un buen uso de la memoria caché puede resultar en aumentos dramáticos de la velocidad en comparación con la optimización de la copia impar o el desenrollado de un bucle.
Como siempre, sin embargo, lo más importante es el perfil. Solo optimice el código que es realmente lento, asegúrese de que su optimización en realidad lo haga más rápido y observe el desensamblaje para ver qué optimizaciones ya está haciendo el compilador antes de intentar mejorarlo.
Si no hay costo para la optimización, hazlo. Al escribir el código, ++i
es tan fácil de escribir como i++
, así que prefiera el primero. No hay costo para ello.
Por otro lado, regresar y hacer este cambio después lleva tiempo, y lo más probable es que no haga una diferencia notable, por lo que probablemente no debería molestarse en hacerlo.
Pero sí, puede hacer una diferencia. En los tipos incorporados, probablemente no, pero para las clases complejas, es improbable que el compilador pueda optimizarlo. La razón de esto es que la operación de incremento no ya no es una operación intrínseca, integrada en el compilador, sino una función definida en la clase. El compilador puede ser capaz de optimizarlo como cualquier otra función, pero no puede, en general, asumir que se puede usar pre-incremento en lugar de post-incremento. Las dos funciones pueden hacer cosas completamente diferentes.
Entonces, al determinar qué optimizaciones puede realizar el compilador, considere si tiene suficiente información para realizarla. En este caso, el compilador no sabe que el incremento posterior y el incremento previo realizan las mismas modificaciones al objeto, por lo que no puede suponer que una pueda reemplazarse con la otra. Pero tiene este conocimiento, por lo que puede realizar la optimización de forma segura.
El compilador puede hacer muchas de las otras que mencionas, por lo general: el compilador puede hacer la alineación, y por lo general es mejor que tú. Todo lo que necesita saber es qué tan grande es la proporción de la función que consiste en la llamada a la función y ¿con qué frecuencia se llama? Una función grande que se llama a menudo probablemente no debería estar en línea, porque terminas copiando una gran cantidad de código, lo que resulta en un ejecutable más grande y más memoria caché de instrucciones. Alinear es siempre una compensación, y, a menudo, el compilador es mejor sopesando todos los factores que usted.
El desenrollado de bucles es una operación puramente mecánica, y el compilador puede hacer eso fácilmente. Lo mismo ocurre con la reducción de la fuerza. El intercambio de los bucles interno y externo es más complicado, ya que el compilador debe probar que el orden de desplazamiento modificado no afectará el resultado, lo cual es difícil de hacer automáticamente. Así que aquí hay una optimización que debe hacer usted mismo.
Pero incluso en los simples que el compilador puede hacer, a veces tienes información que tu compilador no tiene. Si sabe que una función se llamará con mucha frecuencia, incluso si solo se llama desde un lugar, puede valer la pena comprobar si el compilador la alinea automáticamente, y hacerlo de forma manual si no.
A veces, también puede saber más sobre un bucle que el compilador (por ejemplo, que el número de iteraciones siempre será un múltiplo de 4, por lo que puede desenrollarlo con seguridad 4 veces). Es posible que el compilador no tenga esta información, por lo que si tuviera que alinear el bucle, tendría que insertar un epílogo para asegurarse de que las últimas iteraciones se realicen correctamente.
Por lo tanto, tales optimizaciones a "pequeña escala" pueden seguir siendo necesarias, si 1) realmente necesita el rendimiento, y 2) tiene información que el compilador no necesita.
No puede superar al compilador en optimizaciones puramente mecánicas. Pero es posible que pueda hacer suposiciones que el compilador no puede, y es cuando puede optimizar mejor que el compilador.
Solo si sabes con certeza que son relevantes. Esto significa que ya ha investigado este problema en su compilador particular o ya ha hecho lo siguiente:
- código funcional producido
- perfilado ese código
- cuellos de botella identificados
- Diseño simplificado para eliminar cuellos de botella.
- Algoritmos elegidos que minimizan las llamadas a cuellos de botella.
Si ha hecho todas estas cosas, entonces lo mejor que puede hacer es lograr que su compilador emita algo a un nivel inferior que pueda examinar usted mismo (como ensamblaje) y hacer juicios específicos basados en eso. En mi experiencia, cada compilador es un poco diferente. A veces, una optimización en uno hace que otro produzca código más grande y menos eficiente.
Si aún no ha hecho estas cosas, lo llamaría optimización prematura y lo recomendaría. La optimización antes de hacer estas cosas da recompensas que son desproporcionadamente pequeñas en comparación con el costo involucrado.
También se debe tener cuidado de que el cambio de operadores pre / post-incremento / decremento no presente un efecto secundario indeseable. Por ejemplo, si está iterando sobre un bucle 5 veces simplemente para ejecutar un conjunto de códigos varias veces sin ningún interés en el valor del índice del bucle, probablemente esté bien (YMMV). Por otro lado, si accede al valor del índice de bucle, es posible que el resultado no sea el esperado:
#include <iostream>
int main()
{
for (unsigned int i = 5; i != 0; i--)
std::cout << i << std::endl;
for (unsigned int i = 5; i != 0; --i)
std::cout << "/t" << i << std::endl;
for (unsigned int i = 5; i-- != 0; )
std::cout << i << std::endl;
for (unsigned int i = 5; --i != 0; )
std::cout << "/t" << i << std::endl;
}
resultados en lo siguiente:
5
4
3
2
1
5
4
3
2
1
4
3
2
1
0
4
3
2
1
Los dos primeros casos no muestran ninguna diferencia, pero observe que intentar "optimizar" el cuarto caso al cambiar a un operador de decremento previo resultaría en una iteración completamente perdida. Es cierto que este es un caso un tanto artificial, pero he visto este tipo de iteración de bucle (tercer caso) al pasar por una matriz en orden inverso, es decir, de un extremo a otro.
Todas las optimizaciones que enumeró son prácticamente irrelevantes en estos días para los programadores de C: el compilador es mucho mejor en la ejecución de tareas como alineación, desenrollado de bucles, atascos de bucles, inversión de bucles y reducción de fuerza.
Con respecto a ++i
versus i++
: para los enteros, generan un código de máquina idéntico, por lo que el que use es una cuestión de estilo / preferencia. En C ++, los objetos pueden sobrecargar los operadores pre y postincremento, en cuyo caso generalmente es preferible usar un preincremento, porque un postincremento requiere una copia de objeto adicional.
En cuanto al uso de turnos en lugar de multiplicaciones por potencias de 2, de nuevo, el compilador ya lo hace por ti. Dependiendo de la arquitectura, puede hacer cosas aún más inteligentes, como convertir una multiplicación por 5 en una sola instrucción de lea
en x86. Sin embargo, con divisiones y módulos de potencias de 2, es posible que deba prestar un poco más de atención para obtener el código óptimo. Supongamos que usted escribe:
x = y / 2;
Si x
y y
son enteros con signo, el compilador no puede convertir eso en un cambio a la derecha porque dará un resultado erróneo para los números negativos. Por lo tanto, emite un cambio a la derecha y algunas instrucciones de giro de bits para asegurarse de que el resultado sea correcto tanto para los números positivos como para los negativos. Si sabes que x
y y
siempre son positivos, entonces debes ayudar al compilador y convertirlos en enteros sin signo. Luego, el compilador puede optimizarlo en una sola instrucción de cambio a la derecha.
El operador de módulo %
funciona de manera similar: si está modding por una potencia de 2, con enteros con signo, el compilador debe emitir una instrucción y un poco más de bits para hacer que el resultado sea correcto para números positivos y negativos, pero puede Emitir una sola and
instrucción si se trata de números sin firmar.
Una observación interesante que he tenido a lo largo de los años es que el código optimizado de una generación anterior parece estar realmente optimizado a la inversa en la siguiente generación. Esto se debe, por ejemplo, a que las implementaciones del procesador cambian de modo que si / else se convierte en un cuello de botella en las CPU modernas, donde las tuberías son profundas. Yo diría que un código limpio, corto y conciso es a menudo el mejor resultado final. Donde la optimización realmente cuenta es en las estructuras de datos, para que sean correctas y delgadas.
En primer lugar - siempre ejecute perfilado para comprobar.
En primer lugar si optimizas parte correcta del código. Si el código se ejecuta en un 1% del tiempo total, olvídalo. Incluso si lo aumentas en un 50%, obtienes una aceleración total de 0.5%. A menos que esté haciendo algo extraño, la aceleración será mucho más lenta (especialmente si utilizó un buen compilador de optimización). En segundo lugar si lo optimizas bien. ¿Qué código se ejecutaría más rápido en x86?
inc eax
o
add eax, 1
Bien. Por lo que sé, en los procesadores anteriores, el primero, pero en P4 el segundo (es irrelevante aquí si esas instrucciones específicas se ejecutan más rápido o más lento, el punto es que cambia todo el tiempo). El compilador puede estar al día con tales cambios, usted no lo hará.
En mi opinión, el objetivo principal es la optimización que no puede realizar el compilador, como se mencionó anteriormente, el tamaño de los datos (usted puede pensar que no es necesario en las computadoras 2 GiB de hoy en día), pero si sus datos son más grandes que el caché del procesador, se ejecutarán mucho más lento).
En general, hágalo solo si debe y / o sabe lo que está haciendo. Requerirá una cantidad de conocimiento sobre el código, el compilador y la arquitectura de computadora de bajo nivel que no se menciona en la pregunta (y para ser honesto, no poseo). Y es probable que no gane nada. Si quieres optimizar, hazlo en un nivel más alto.
Todavía hago cosas como ra << = 1; en lugar de ra * = 2; Y continuará haciéndolo. Pero los compiladores (tan malos como son) y lo que es más importante, la velocidad de las computadoras es tan rápida que estas optimizaciones a menudo se pierden en el ruido. Como pregunta general, no vale la pena, si está específicamente en una plataforma de recursos limitados (por ejemplo, un microcontrolador) en el que cada reloj adicional realmente cuenta, probablemente ya haga esto y probablemente realice una buena cantidad de ajustes de ensamblador. Como hábito, trato de no darle demasiado trabajo adicional al compilador, pero para la legibilidad y confiabilidad del código no me salgo de mi camino.
La línea de fondo para el rendimiento nunca ha cambiado. Encuentre alguna manera de cronometrar su código, mida para encontrar la fruta de bajo rendimiento y arréglela. Mike D. golpeó el clavo en la cabeza en su respuesta. Muchas veces he visto a personas preocuparse por líneas de código específicas sin darse cuenta de que están utilizando un compilador deficiente o al cambiar una opción de compilador que podrían ver varias veces un aumento en el rendimiento de la ejecución.
En general, no optimizo una complejidad inferior a la O (f (n)) a menos que esté escribiendo en un dispositivo integrado.
Para el trabajo típico de g ++ / Visual Studio, supongo que las optimizaciones básicas se realizarán de manera confiable (al menos cuando se solicite optimización). Para los compiladores menos maduros, ese supuesto es presumiblemente no válido.
Si estuviera haciendo un trabajo pesado de matemáticas en la transmisión de datos, verificaría la capacidad del compilador para emitir instrucciones SIMD.
Prefiero ajustar mi código en función de algoritmos diferentes a una versión específica de un compilador específico. Los algoritmos resistirán la prueba de múltiples procesadores / compiladores, mientras que si ajusta la versión 2008 de Visual C ++ (primer lanzamiento), es posible que sus optimizaciones ni siquiera funcionen el próximo año.
Ciertos trucos de optimización que son muy razonables en computadoras antiguas demuestran tener problemas en la actualidad. Por ejemplo, los operadores ++ / ++ se diseñaron alrededor de una arquitectura más antigua que tenía una instrucción de incremento que fue muy rápida. Hoy, si haces algo como
for(int i = 0; i < top; i+=1)
Supongo que el compilador se optimizaría
i+=1
en unainc
instrucción (si la CPU lo tuviera).El consejo clásico es optimizar de arriba hacia abajo.