gcc flags
¿Cómo evitar que GCC optimice un ciclo de espera ocupado? (8)
Declara las variables i
y j
como volatile
. Esto evitará que el compilador optimice el código que involucra estas variables.
unsigned volatile char i, j;
Quiero escribir un firmware de código C para los microcontroladores Atmel AVR. Lo compilaré usando GCC. Además, quiero habilitar las optimizaciones del compilador ( -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, coloqueasm ("");
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á compilando 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) .
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 compilador, 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 compilador 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 compilador no pudo ver al compilar el "programa 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 programa 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.
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:
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);
}
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 ...