¿Un bucle infinito dentro del bucle () funcionaría más rápido?

19

Cuando escribes un boceto típico, usualmente confías en loop()que te llamen repetidamente durante todo el tiempo que Arduino esté funcionando. Sin loop()embargo, moverse dentro y fuera de la función debe introducir una pequeña sobrecarga.

Para evitar eso, presumiblemente podría crear su propio bucle infinito, así:

void loop()
{
    while (true)
    {
        // do stuff...
    }
}

¿Es esa una forma viable de mejorar el rendimiento? ¿Causará otros problemas si loop()nunca regresa?

Peter Bloomfield
fuente

Respuestas:

18

La parte del código en un núcleo ATmega que hace setup () y loop () es la siguiente:

#include <Arduino.h>

int main(void)
{
        init();

#if defined(USBCON)
        USBDevice.attach();
#endif

        setup();

        for (;;) {
                loop();
                if (serialEventRun) serialEventRun();
        }

        return 0;
}

Bastante simple, pero existe la sobrecarga de serialEventRun (); ahí.

Comparemos dos bocetos simples:

void setup()
{

}

volatile uint8_t x;

void loop()
{

    x = 1;

}

y

void setup()
{

}

volatile uint8_t x;

void loop()
{
    while(true)
    {
        x = 1;
    }
}

La x y volátil es solo para garantizar que no esté optimizada.

En el ASM producido, obtienes resultados diferentes: Comparación de dos

Puede ver el while (verdadero) solo realiza un rjmp (salto relativo) unas pocas instrucciones, mientras que loop () realiza una resta, comparación y llamada. Estas son 4 instrucciones vs 1 instrucción.

Para generar ASM como se indicó anteriormente, debe usar una herramienta llamada avr-objdump. Esto se incluye con avr-gcc. La ubicación varía según el sistema operativo, por lo que es más fácil buscarla por su nombre.

avr-objdump puede operar en archivos .hex, pero a estos les faltan la fuente original y los comentarios. Si acaba de crear código, tendrá un archivo .elf que contiene estos datos. Una vez más, la ubicación de estos archivos varía según el sistema operativo: la forma más fácil de localizarlos es activar la compilación detallada en las preferencias y ver dónde se almacenan los archivos de salida.

Ejecute el comando de la siguiente manera:

avr-objdump -S output.elf> asm.txt

Y examine la salida en un editor de texto.

Cybergibbons
fuente
OK, pero ¿no hay una razón para llamar a la función serialEventRun ()? ¿Para qué sirve?
jfpoilpret
1
Es parte de la funcionalidad utilizada por HardwareSerial, no estoy seguro de por qué no se saca cuando no se necesita Serial.
Cybergibbons
2
Sería útil explicar brevemente cómo generó la salida de ASM para que las personas puedan comprobarse a sí mismas.
jippie
@Cybergibbons nunca se saca porque es parte del estándar main.cutilizado por Arduino IDE. Sin embargo, no significa que la biblioteca HardwareSerial esté incluida en su boceto; En realidad no se incluye si no lo usa (por eso existe if (serialEventRun)en main()la función Si no se utiliza la biblioteca HardwareSerial a continuación. serialEventRunserá nula, por lo tanto, no hay ninguna llamada.
jfpoilpret
1
Sí, es parte de main.c como se cita, pero esperaría que se optimice si no es necesario, por lo tanto, creo que siempre se incluyen aspectos de Serial. Con frecuencia escribo código que nunca volverá de loop () y no noto problemas con Serial.
Cybergibbons
6

La respuesta de Cybergibbons describe muy bien la generación del código de ensamblaje y las diferencias entre las dos técnicas. Se pretende que sea una respuesta complementaria que analice el problema en términos de diferencias prácticas , es decir, cuánta diferencia tendrá cada enfoque en términos de tiempo de ejecución .


Variaciones de código

Hice un análisis que involucra las siguientes variaciones:

  • Básico void loop()(que se integra en la compilación)
  • No en línea void loop()(usando __attribute__ ((noinline)))
  • Bucle con while(1)(que se optimiza)
  • Bucle con no optimizado while(1)(agregando __asm__ __volatile__("");. Esta es una nopinstrucción que evita la optimización del bucle sin generar gastos generales adicionales de una volatilevariable)
  • Un no en línea void loop()con optimizadowhile(1)
  • Un no en línea void loop()con no optimizadowhile(1)

Los bocetos se pueden encontrar aquí .

Experimentar

Ejecuté cada uno de estos bocetos durante 30 segundos, acumulando así 300 puntos de datos cada uno . Hubo una delayllamada de 100 milisegundos en cada bucle (sin el cual suceden cosas malas ).

Resultados

Luego calculé los tiempos de ejecución medios de cada ciclo, resté 100 milisegundos de cada uno y luego grafiqué los resultados.

http://raw2.github.com/AsheeshR/Arduino-Loop-Analysis/master/Figures/timeplot.png

Conclusión

  • Un while(1)bucle no optimizado void loopes más rápido que un compilador optimizado void loop.
  • La diferencia horaria entre el código no optimizado y el código optimizado predeterminado de Arduino es prácticamente insignificante . Será mejor compilar manualmente usando avr-gccy usando sus propios indicadores de optimización en lugar de depender del IDE de Arduino para ayudarlo (si necesita optimizaciones de microsegundos).

NOTA: Los valores de tiempo reales no son significativos aquí, la diferencia entre ellos sí lo es. Los ~ 90 microsegundos de tiempo de ejecución incluyen una llamada a Serial.println, microsy delay.

NOTA2: Esto se realizó utilizando el IDE de Arduino y los indicadores de compilación predeterminados que proporciona.

NOTA3: El análisis (gráfico y cálculos) se realizó utilizando R.

Asheeshr
fuente
1
Buen trabajo. Graph tiene milisegundos, no microsegundos, pero no es un gran problema.
Cybergibbons
@Cybergibbons Eso es muy poco probable ya que todas las medidas están en microsegundos y que no han cambiado en cualquier lugar escalas :)
asheeshr