¿Es realmente una buena práctica desactivar las optimizaciones durante las fases de desarrollo y depuración?

15

He leído Programación de microcontroladores PIC de 16 bits en C , y hay esta afirmación en el libro:

Sin embargo, durante las fases de desarrollo y depuración de un proyecto, siempre es una buena práctica deshabilitar todas las optimizaciones, ya que podrían modificar la estructura del código que se analiza y hacer que la colocación de puntos de interrupción y paso único sea problemática.

Confieso que estaba un poco confundido. No entendí si el autor dijo eso debido al período de evaluación C30 o si realmente es una buena práctica.

Me gustaría saber si realmente utilizas esta práctica y por qué.

Daniel Grillo
fuente

Respuestas:

16

Esto es bastante estándar en la ingeniería de software en su conjunto: cuando optimiza el código, el compilador puede reorganizar las cosas más o menos como lo desee, siempre que no pueda notar ninguna diferencia en la operación. Entonces, por ejemplo, si inicializa una variable dentro de cada iteración de un bucle, y nunca cambia la variable dentro del bucle, el optimizador puede mover esa inicialización fuera del bucle, para que no pierda el tiempo con ella.

También puede darse cuenta de que calcula un número con el que luego no hace nada antes de sobrescribir. En ese caso, podría eliminar la computación inútil.

El problema con la optimización es que querrás poner un punto de interrupción en algún fragmento de código que el optimizador haya movido o eliminado. En ese caso, el depurador no puede hacer lo que desea (generalmente, pondrá el punto de interrupción en algún lugar cercano). Por lo tanto, para hacer que el código generado se parezca más a lo que escribió, desactive las optimizaciones durante la depuración; esto asegura que el código que desea romper está realmente allí.

Sin embargo, debe tener cuidado con esto, ya que dependiendo de su código, ¡la optimización puede romper las cosas! En general, el código que se rompe con un optimizador que funciona correctamente es realmente solo un código defectuoso que se salta con la suya, por lo que generalmente desea averiguar por qué el optimizador lo rompe.

Michael Kohne
fuente
55
El contrapunto a este argumento es que el optimizador probablemente hará que las cosas sean más pequeñas y / o más rápidas, y si tiene un código restringido de tiempo o tamaño, entonces puede romper algo al deshabilitar la optimización y perder su tiempo depurando un problema que no funciona. Realmente existe. Por supuesto, el depurador también puede hacer que su código sea más lento y más grande.
Kevin Vermeer
¿Dónde puedo aprender más sobre esto?
Daniel Grillo
No he trabajado con el compilador C30 pero para el compilador C18 había una nota / manual de aplicación para el compilador que cubría qué optimizaciones soportaba.
Mark
@O Engenheiro: compruebe los documentos de su compilador para saber qué optimizaciones admite. La optimización varía enormemente según el compilador, las bibliotecas y la arquitectura de destino.
Michael Kohne el
Nuevamente, no para el compilador C30, pero gcc publica una larga lista de las diversas optimizaciones que se pueden aplicar. También puede usar esta lista para obtener una optimización detallada, en caso de que tenga una estructura de control particular que desee mantener intacta. La lista está aquí: gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Kevin Vermeer
7

Le envié esta pregunta a Jack Ganssle y esto es lo que me respondió:

Daniel

Prefiero depurar usando cualquier optimización que haya en el código publicado. La NASA dice "prueba lo que vuelas, vuela lo que pruebas". En otras palabras, ¡no hagas pruebas y luego cambia el código!

Sin embargo, a veces uno tiene que desactivar las optimizaciones para que el depurador funcione. Intento desactivarlos solo en los módulos en los que estoy trabajando. Por esa razón, creo en mantener archivos pequeños, digamos unos cientos de líneas de código más o menos.

Todo lo mejor, Jack.

Daniel Grillo
fuente
Creo que hay una distinción no establecida entre los dos párrafos en esta respuesta. Las pruebas se refieren a un procedimiento que se supone que demuestra que los productos blandos, firmes y / o duros funcionan correctamente. La depuración es el proceso en el que el código se pasa por instrucciones por instrucciones para ver por qué no funciona [todavía].
Kevin Vermeer el
Es bueno tener una opción. Por lo tanto, las pruebas pueden abarcar más variedades / permutaciones con y sin optimización. Más cobertura, mejor es la prueba
@reemrevnivek, cuando estás depurando, ¿no estás probando también?
Daniel Grillo
@O Engenheiro - No. Solo depuro si la prueba falla.
Kevin Vermeer
6

Depende, y esto es generalmente cierto para todas las herramientas, no solo C30.

Las optimizaciones a menudo eliminan y / o reestructuran el código de varias maneras. Su declaración de cambio puede volver a implementarse con una construcción if / else o, en algunos casos, puede eliminarse por completo. y = x * 16 puede ser reemplazado por una serie de desplazamientos a la izquierda, etc. aunque este último tipo de optimización generalmente puede ser atravesado, es principalmente la reestructuración de la declaración de control lo que te da.

Esto puede hacer que sea imposible pasar un depurador a través de su código C porque las estructuras que definió en C ya no existen, fueron reemplazadas o reordenadas por el compilador en algo que el compilador cree que será más rápido o usará menos espacio. También puede hacer que los puntos de interrupción sean imposibles de establecer desde la lista C ya que la instrucción en la que rompió ya no existe. Por ejemplo, puede intentar establecer un punto de interrupción dentro de una instrucción if, pero el compilador puede haber eliminado ese if. Puede intentar establecer un punto de interrupción dentro de un tiempo o para un bucle, pero el compilador decidió desenrollar ese bucle para que ya no exista.

Por esta razón, si puede depurar con optimizaciones desactivadas, generalmente es más fácil. Siempre debe volver a realizar pruebas con optimizaciones activadas. Esta es la única forma en que descubrirás que te perdiste un importante volatiley está causando fallas intermitentes (o alguna otra rareza).

En el caso del desarrollo integrado, debe tener cuidado con las optimizaciones de todos modos. Específicamente en secciones de código que son críticas en el tiempo, algunas interrupciones, por ejemplo. En estos casos, debe codificar los bits críticos en el ensamblaje o usar las directivas del compilador para asegurarse de que estas secciones no estén optimizadas para que sepa que tienen un tiempo de ejecución fijo o un tiempo de ejecución del peor de los casos fijo.

El otro problema puede ser ajustar el código en el uC, es posible que necesite optimizaciones de densidad de código para simplemente ajustar su código en el chip. Esta es una de las razones por las cuales, por lo general, es una buena idea comenzar con la capacidad de ROM uC más grande de una familia y solo elegir una más pequeña para la fabricación, después de que su código esté bloqueado.

marca
fuente
5

En general, depuraría con cualquier configuración con la que planeara lanzar. Si iba a lanzar código optimizado, lo depuraría con código optimizado. Si iba a liberar código no optimizado, lo depuraría con código no optimizado. Hago esto por dos razones. Primero, los optimizadores pueden hacer diferencias de tiempo lo suficientemente significativas como para hacer que el producto final se comporte de manera diferente al código no optimizado. En segundo lugar, aunque la mayoría son bastante buenos, los proveedores de compiladores cometen errores y el código optimizado puede producir resultados diferentes del código no optimizado. Como resultado, me gusta obtener el mayor tiempo de prueba posible con cualquier configuración con la que planee lanzar.

Dicho esto, los optimizadores pueden dificultar la depuración como se señaló en las respuestas anteriores. Si encuentro una sección particular de código que es difícil de depurar, apagaré temporalmente el optimizador, realizaré la depuración para que el código funcione, luego volveré a encender el optimizador y probaré una vez más.

semaj
fuente
1
Pero un solo paso del código puede ser casi imposible con las optimizaciones activadas. Depure con optimizaciones desactivadas y ejecute sus pruebas unitarias con código de lanzamiento.
Rocketmagnet
3

Mi estrategia normal es desarrollar con la optimización final (máximo para el tamaño o la velocidad, según corresponda), pero desactive temporalmente la optimización si necesito depurar o rastrear algo. Esto reduce el riesgo de que surjan errores como resultado del cambio de los niveles de optimización.

Un modo de falla típico es cuando el aumento de la optimización hace que surjan errores no vistos anteriormente debido a que no ha declarado que las variables sean volátiles cuando sea necesario; esto es esencial para decirle al compilador qué cosas no deben 'optimizarse'.

mikeselectricstuff
fuente
2

Use cualquier forma con la que va a lanzar, los depuradores y la compilación para la depuración ocultan muchos (MUCHOS) errores que no ve hasta que compila para el lanzamiento. Para entonces, es mucho más difícil encontrar esos errores, en lugar de depurarlos a medida que avanza. 20 años y algo ahora y nunca tuve un uso para un gdb u otro como depurador, no es necesario mirar variables o un solo paso. Cientos a miles de líneas por día. Por lo tanto, es posible, no se deje llevar a pensar lo contrario.

La compilación para la depuración y luego la compilación para el lanzamiento puede y llevará dos veces más de dos veces el esfuerzo. Si tiene problemas y tiene que usar una herramienta como un depurador, compile para que el depurador resuelva el problema específico y luego vuelva a la operación normal.

Otros problemas también son ciertos, ya que el optimizador hace que el código sea más rápido, por lo que para incrustar en particular sus cambios de tiempo con las opciones del compilador y eso puede afectar la funcionalidad de su programa, aquí nuevamente use la opción de compilación entregable durante toda la fase. Los compiladores también son programas y tienen errores y optimizadores que cometen errores y algunos no tienen fe en eso. Si ese es el caso, no hay nada de malo en compilar sin optimización, simplemente hágalo de esa manera todo el tiempo. La ruta que prefiero es compilar para la optimización, entonces si sospecho que un problema del compilador deshabilita la optimización si eso soluciona, generalmente va y viene a veces examinando la salida del ensamblador para descubrir por qué.

viejo contador de tiempo
fuente
1
+1 solo para elaborar su buena respuesta: a menudo compilar en modo "depuración" rellenará la pila / montón alrededor de variables con espacio no asignado para mitigar los pequeños errores de cadena de escritura y formateo. Con mayor frecuencia obtendrá un buen bloqueo de tiempo de ejecución si compila en la versión.
Morten Jensen
2

Siempre desarrollo código con -O0 (opción gcc para desactivar la optimización). Cuando siento que estoy en el punto en el que quiero comenzar a permitir que las cosas se encaminen más hacia un lanzamiento, comenzaré con -Os (optimizar para el tamaño) ya que generalmente cuanto más código pueda mantener en caché, mejor será, incluso si no está súper optimizado.

Me parece que gdb funciona mucho mejor con el código -O0, y es mucho más fácil de seguir si tiene que ingresar al ensamblado. Alternar entre -O0 y -Os también le permite ver lo que el compilador le está haciendo a su código. A veces es una educación bastante interesante, y también puede descubrir errores de compilación ... ¡esas cosas desagradables que te hacen arrancarte el pelo tratando de descubrir qué tiene de malo tu código!

Si realmente lo necesito, comenzaré a agregar en -fdata-secciones y -fcode-secciones con --gc-secciones, lo que permite que el enlazador elimine funciones completas y segmentos de datos que no se utilizan realmente. Hay muchas pequeñas cosas con las que puedes jugar para tratar de reducir las cosas más rápido o hacer las cosas más rápido, pero en general, estos son los únicos trucos que termino usando, y cualquier cosa que tenga que ser más pequeña o más rápida. -montar.

akohlsmith
fuente
2

Sí, deshabilitar las optimizaciones durante la depuración ha sido la mejor práctica durante un tiempo, por tres razones:

  • (a) si va a realizar un solo paso del programa con un depurador de alto nivel, es un poco menos confuso.
  • (a) (obsoleto) si va a depurar el programa en un solo paso con un depurador en lenguaje ensamblador, es mucho menos confuso. (¿Pero por qué molestarse con esto cuando podría estar usando un depurador de alto nivel?)
  • (b) (obsoleto durante mucho tiempo) probablemente solo ejecutará este ejecutable en particular una vez, luego hará algunos cambios y recompilará. Es una pérdida de tiempo para una persona esperar 10 minutos adicionales mientras el compilador "optimiza" este ejecutable en particular, cuando eso ahorrará menos de 10 minutos de tiempo de ejecución. (Esto ya no es relevante con las PC modernas que pueden compilar un ejecutable de microcontrolador típico, con optimización completa, en menos de 2 segundos).

Muchas personas van aún más lejos en esta dirección y envían con afirmaciones activadas .

davidcary
fuente
Un solo paso a través del código de ensamblaje puede ser muy útil para diagnosticar casos en los que el código fuente realmente especifica algo diferente de lo que parece (por ejemplo, "longvar1 & = ~ 0x40000000; longvar2 & = ~ 0x80000000;") o donde un compilador genera código defectuoso . He rastreado algunos problemas usando depuradores de código de máquina que realmente no creo que podría haber rastreado de otra manera.
supercat
2

Simple: las optimizaciones requieren mucho tiempo y pueden ser inútiles si tiene que cambiar ese código más adelante en el desarrollo. Por lo tanto, pueden ser una pérdida de tiempo y dinero.
Sin embargo, son útiles para módulos terminados; partes del código que probablemente ya no necesiten cambios.

stevenvh
fuente
2

Ciertamente tiene sentido en el caso de los puntos de ruptura ... ya que el compilador puede eliminar muchas declaraciones que en realidad no afectan la memoria.

considera algo como:

int i =0;

for (int j=0; j < 10; j++)
{
 i+=j;
}
return 0;

podría optimizarse por completo (porque inunca se lee). Desde el punto de vista de su punto de interrupción, parecería que omitió todo ese código, cuando básicamente ni siquiera estaba allí ... Supongo que es por eso que en las funciones de tipo de suspensión a menudo verá algo como:

for (int j=delay; j != 0; j--)
{
    asm( " nop " );
    asm( " nop " );
}
return 0;
Grady Player
fuente
1

Si está utilizando el depurador, deshabilitaría las optimizaciones y habilitaría la depuración.

Personalmente, encuentro que el depurador PIC causa más problemas de los que me ayuda a solucionar.
Solo uso printf () para USART para depurar mis programas escritos en C18.

mjh2007
fuente
1

La mayoría de los argumentos en contra de habilitar la optimización en su compilación se reducen a:

  1. problemas con la depuración (conectividad JTAG, puntos de interrupción, etc.)
  2. sincronización de software incorrecta
  3. sh * t deja de funcionar correctamente

En mi humilde opinión, los dos primeros son legítimos, el tercero no tanto. A menudo significa que tiene un código incorrecto o depende de una explotación insegura del lenguaje / implementación o tal vez el autor es solo un fanático del buen comportamiento indefinido del tío.

El blog Embedded in Academia tiene una o dos cosas que decir sobre el comportamiento indefinido, y esta publicación trata sobre cómo los compiladores lo explotan: http://blog.regehr.org/archives/761

Morten Jensen
fuente
Otra posible razón es que el compilador puede ser molestamente lento cuando se activan las optimizaciones.
supercat