c++ - ¿Es peligroso el nivel de optimización-O3 en g++?
gcc flags (5)
En los primeros días de gcc (2.8, etc.) y en los tiempos de egcs, y redhat 2.96 -O3 a veces tenía muchos errores. Pero esto fue hace más de una década, y -O3 no es muy diferente a otros niveles de optimizaciones (en buggyness).
Sin embargo, tiende a revelar los casos en que las personas confían en un comportamiento indefinido, debido a que confían más estrictamente en las reglas, y especialmente en los casos de esquina, de los idiomas.
Como nota personal, llevo muchos años ejecutando software de producción en el sector financiero con -O3 y aún no he encontrado un error que no habría estado allí si hubiera usado -O2.
Por demanda popular, aquí una adición:
-O3 y especialmente las banderas adicionales como -funroll-loops (no habilitado por -O3) a veces pueden llevar a que se genere más código de máquina. Bajo ciertas circunstancias (por ejemplo, en una CPU con un caché de instrucciones L1 excepcionalmente pequeño), esto puede causar una desaceleración debido a que todo el código de, por ejemplo, algún bucle interno ya no se ajusta a L1I. En general, gcc intenta no generar tanto código, pero dado que generalmente optimiza el caso genérico, esto puede suceder. Las opciones especialmente propensas a esto (como el desenrollado de bucle) normalmente no se incluyen en -O3 y se marcan en consecuencia en la página del manual. Como tal, generalmente es una buena idea usar -O3 para generar código rápido, y solo recurrir a -O2 o -Os (que intenta optimizar el tamaño del código) cuando sea apropiado (por ejemplo, cuando un generador de perfiles indica que L1I no se encuentra).
Si desea llevar la optimización al extremo, puede modificar gcc a través de --param los costos asociados con ciertas optimizaciones. Además, tenga en cuenta que gcc ahora tiene la capacidad de poner atributos en las funciones que controlan la configuración de optimización solo para estas funciones, por lo que cuando descubra que tiene un problema con -O3 en una función (o desea probar marcas especiales para esa función), no es necesario compilar todo el archivo o incluso todo el proyecto con O2.
otoh parece que se debe tener cuidado al usar -Oast, que establece:
-Rápido habilita todas las optimizaciones -O3. También permite las optimizaciones que no son válidas para todos los programas compatibles con el estándar.
lo que me lleva a la conclusión de que -O3 está destinado a cumplir con todos los estándares.
He escuchado de varias fuentes (aunque en su mayoría de un colega mío), que compilar con un nivel de optimización de -O3
en g ++ es de alguna manera "peligroso", y debe evitarse en general a menos que se demuestre que es necesario.
¿Es esto cierto, y si es así, por qué? ¿Debería estar -O2
a -O2
?
Esto ya se dijo en la respuesta de Neel, pero no lo suficientemente clara o lo suficientemente fuerte:
En mi experiencia un tanto confusa, la aplicación de -O3
a un programa completo casi siempre lo hace más lento (en relación con -O2
), porque activa el desenrollamiento agresivo del bucle y la alineación que hace que el programa ya no encaje en el caché de instrucciones. Para programas más grandes, esto también puede ser cierto para -O2
relativo a -Os
!
El patrón de uso previsto para -O3
es, luego de perfilar su programa, lo aplica manualmente a un pequeño puñado de archivos que contienen bucles internos críticos que realmente se benefician de estos intercambios agresivos de espacio por velocidad. Con el GCC muy reciente, creo que el nuevo y brillante modo de optimización guiada por perfil de tiempo de enlace puede aplicar de forma selectiva las optimizaciones -O3
a las funciones en caliente, automatizando efectivamente este proceso.
La opción -O3 activa optimizaciones más caras, como la función de alineación, además de todas las optimizaciones de los niveles inferiores ''-O2'' y ''-O1''. El nivel de optimización ''-O3'' puede aumentar la velocidad del ejecutable resultante, pero también puede aumentar su tamaño. En algunas circunstancias donde estas optimizaciones no son favorables, esta opción podría hacer que un programa sea más lento.
Recientemente experimenté un problema al utilizar la optimización con g++
. El problema estaba relacionado con una tarjeta PCI, donde los registros (para comando y datos) estaban representados por una dirección de memoria. Mi conductor asignó la dirección física a un puntero dentro de la aplicación y la entregó al proceso llamado, que funcionó de esta manera:
unsigned int * pciMemory;
askDriverForMapping( & pciMemory );
...
pciMemory[ 0 ] = someCommandIdx;
pciMemory[ 0 ] = someCommandLength;
for ( int i = 0; i < sizeof( someCommand ); i++ )
pciMemory[ 0 ] = someCommand[ i ];
La tarjeta no actuó como se esperaba. Cuando vi el ensamblaje, comprendí que el compilador solo escribía algo someCommand[ the last ]
en pciMemory
, omitiendo todas las escrituras anteriores.
En conclusión: ser precisos y atentos con la optimización.
Sí, O3 tiene más errores. Soy un desarrollador de compiladores y he identificado errores claros y evidentes de gcc causados por O3 que generan instrucciones de ensamblaje de SIMD con errores al compilar mi propio software. Por lo que he visto, la mayoría del software de producción viene con O2, lo que significa que O3 recibirá menos atención en las pruebas de corrección y en la corrección de errores.
Piénselo de esta manera: O3 agrega más transformaciones sobre O2, lo que agrega más transformaciones sobre O1. Estadísticamente hablando, más transformaciones significa más errores. Eso es cierto para cualquier compilador.