¿Cómo evitar que GCC optimice un ciclo de espera ocupado?

Quiero escribir un firmware de código C para los microcontroladores Atmel AVR. Lo comstackré usando GCC. Además, quiero habilitar las optimizaciones del comstackdor ( -Os o -O2 ), ya que no veo ninguna razón para no habilitarlas, y probablemente generarán una mejor forma de ensamblaje más rápido que escribir el ensamblaje manualmente.

Pero quiero un pequeño código no optimizado. Deseo retrasar la ejecución de una función por algún tiempo, y por lo tanto, quería escribir un ciclo de no hacer nada para perder el tiempo. No es necesario ser preciso, solo espera un tiempo.

 /* How to NOT optimize this, while optimizing other code? */ unsigned char i, j; j = 0; while(--j) { i = 0; while(--i); } 

Como el acceso a la memoria en AVR es mucho más lento, quiero que i y j se guarden en los registros de la CPU.


Actualización: Acabo de encontrar util / delay.h y util / delay_basic.h de AVR Libc . Aunque la mayoría de las veces puede ser una mejor idea usar esas funciones, esta pregunta sigue siendo válida e interesante.

Desarrollé esta respuesta después de seguir un enlace de la respuesta de dmckee , pero toma un enfoque diferente al de su respuesta.

La documentación de Atributos de función de las menciones de GCC:

noinline Este atributo de función evita que se considere una función para alinear. Si la función no tiene efectos secundarios, existen optimizaciones distintas de la línea interna que hacen que las llamadas a funciones se optimicen, aunque la llamada a la función es en vivo. Para evitar que esas llamadas se optimicen, coloque asm ("");

Esto me dio una idea interesante … En lugar de agregar una instrucción nop en el ciclo interno, intenté agregar un código ensamblador vacío, como este:

 unsigned char i, j; j = 0; while(--j) { i = 0; while(--i) asm(""); } 

¡Y funcionó! Ese bucle no se ha optimizado y no se insertaron instrucciones adicionales.

Es más, si usa volatile , gcc almacenará esas variables en la RAM y agregará un montón de ldd y std para copiarlas en los registros temporales. Este enfoque, por otro lado, no usa volatile y no genera tal sobrecarga.


Actualización: si está comstackndo código usando -ansi o -std , debe reemplazar la palabra clave asm con __asm__ , como se describe en la documentación de GCC .

Además, también puede usar __asm__ __volatile__("") si su instrucción de ensamblado debe ejecutarse donde lo ponemos (es decir, no debe moverse de un bucle como una optimización) .

Declara las variables i y j como volatile . Esto evitará que el comstackdor optimice el código que involucra estas variables.

 unsigned volatile char i, j; 

No estoy seguro de por qué no se ha mencionado aún que este enfoque está completamente mal orientado y se puede romper fácilmente con las actualizaciones del comstackdor, etc. Tendría mucho más sentido determinar el valor de tiempo que desea esperar hasta que se realice un giro y contestar el actual. tiempo hasta que se excede el valor deseado. En x86 puede usar rdtsc para este propósito, pero la forma más portátil sería llamar a clock_gettime (o la variante para su sistema operativo no POSIX) para obtener la hora. Actual x86_64 Linux incluso evitará el syscall para clock_gettime y usar rdtsc internamente. O bien, si puede manejar el costo de un syscall, simplemente use clock_nanosleep para comenzar …

No sé si la versión avr del comstackdor admite el conjunto completo de #pragma (los más interesantes en el enlace datan de la versión 4.4 de gcc), pero ahí es donde normalmente comenzarías.

Para mí, en GCC 4.7.0, el asm vacío se optimizó de todos modos con -O3 (no lo intenté con -O2). y el uso de un i ++ en el registro o volátil resultó en una gran penalización de rendimiento (en mi caso).

Lo que hice fue vincular con otra función vacía que el comstackdor no pudo ver al comstackr el “progtwig principal”

Básicamente esto:

Creado “helper.c” con esta función declarada (función vacía)

 void donotoptimize(){} 

Luego compiló “gcc helper.c -c -o helper.o” y luego

 while (...) { donotoptimize();} 

Esto me dio los mejores resultados (y, desde mi punto de vista, no hay gastos generales, pero no puedo probar porque mi progtwig no funcionará sin él :))

Creo que debería funcionar con icc también. Tal vez no si habilitas las optimizaciones de enlace, pero con gcc sí lo hace.

pon ese bucle en un archivo .c separado y no optimices ese solo archivo. Mejor aún, escriba esa rutina en el ensamblador y llámelo desde C, en cualquier caso, el optimizador no se involucrará.

A veces hago lo volátil pero normalmente creo una función asm que simplemente devuelve poner una llamada a esa función, el optimizador ajustará el ciclo for / while pero no lo optimizará porque tiene que realizar todas las llamadas a la función ficticia. La respuesta nop de Denilson Sá hace lo mismo, pero aún más …

Poner el asm volátil debería ayudar. Puedes leer más sobre esto aquí:

http://www.nongnu.org/avr-libc/user-manual/optimization.html

Si está trabajando en Windows, incluso puede tratar de poner el código bajo pragmas, como se explica en detalle a continuación:

https://www.securecoding.cert.org/confluence/display/cplusplus/MSC06-CPP.+Be+aware+of+compiler+optimization+when+dealing+with+sensitive+data

Espero que esto ayude.

También puede usar la palabra clave register . Las variables declaradas con registro se almacenan en registros de CPU.

En tu caso:

 register unsigned char i, j; j = 0; while(--j) { i = 0; while(--i); }