Un loop infinito all'interno di loop () avrebbe prestazioni più veloci?


19

Quando scrivi uno schizzo tipico, di solito fai affidamento sul fatto di loop()essere chiamato ripetutamente finché Arduino è in esecuzione. Entrare e uscire dalla loop()funzione deve presentare un piccolo sovraccarico.

Per evitarlo, potresti presumibilmente creare il tuo ciclo infinito, in questo modo:

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

È un modo possibile per migliorare le prestazioni? Provocherà altri problemi se loop()non ritorna mai?

Risposte:


18

La parte del codice su un core ATmega che esegue setup () e loop () è la seguente:

#include <Arduino.h>

int main(void)
{
        init();

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

        setup();

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

        return 0;
}

Abbastanza semplice, ma c'è l'overhead di serialEventRun (); lì dentro.

Confrontiamo due semplici schizzi:

void setup()
{

}

volatile uint8_t x;

void loop()
{

    x = 1;

}

e

void setup()
{

}

volatile uint8_t x;

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

La x e volatile è solo per garantire che non sia ottimizzato.

Nell'ASM prodotto, si ottengono risultati diversi: Confronto di due

Puoi vedere mentre while (true) esegue solo un rjmp (salto relativo) indietro di alcune istruzioni, mentre loop () esegue una sottrazione, un confronto e una chiamata. Sono 4 istruzioni contro 1 istruzione.

Per generare ASM come sopra, è necessario utilizzare uno strumento chiamato avr-objdump. Questo è incluso con avr-gcc. La posizione varia a seconda del sistema operativo, quindi è più facile cercarla per nome.

avr-objdump può operare su file .hex, ma a questi mancano la fonte e i commenti originali. Se hai appena creato il codice, avrai un file .elf che contiene questi dati. Ancora una volta, la posizione di questi file varia in base al sistema operativo: il modo più semplice per individuarli è attivare la compilazione dettagliata nelle preferenze e vedere dove vengono archiviati i file di output.

Eseguire il comando come segue:

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

Ed esamina l'output in un editor di testo.


OK, ma non c'è un motivo per chiamare la funzione serialEventRun ()? Cosa serve?
jfpoilpret,

1
Fa parte della funzionalità utilizzata da HardwareSerial, non è sicuro del motivo per cui non viene rimosso quando Serial non è necessario.
Cybergibbons,

2
Sarebbe utile spiegare brevemente come hai generato l'output ASM in modo che le persone possano controllarsi.
jippie,

@Cybergibbons non viene mai rimosso perché fa parte dello standard main.cutilizzato da Arduino IDE. Tuttavia, ciò non significa che la libreria HardwareSerial sia inclusa nel tuo schizzo; in realtà non è incluso se non ne fanno uso (è per questo che v'è if (serialEventRun)in main()funzione Se non si utilizza HardwareSerial biblioteca allora. serialEventRunsarà nullo, quindi nessuna chiamata.
jfpoilpret

1
Sì, fa parte del main.c come citato, ma mi aspetto che sia ottimizzato se non richiesto, quindi penso che gli aspetti di Serial siano sempre inclusi. Scrivo spesso codice che non tornerà mai da loop () e non noto problemi con Serial.
Cybergibbons,

6

La risposta di Cybergibbons descrive abbastanza bene la generazione del codice assembly e le differenze tra le due tecniche. Questa è una risposta complementare che esamina il problema in termini di differenze pratiche , ovvero quanta differenza farà l'uno o l'altro approccio in termini di tempi di esecuzione .


Variazioni del codice

Ho fatto un'analisi che comprende le seguenti variazioni:

  • Base void loop()(che viene inserito nella compilation)
  • Non in linea void loop()(utilizzando __attribute__ ((noinline)))
  • Ripeti con while(1)(che viene ottimizzato)
  • Ciclo con non ottimizzato while(1)(aggiungendo __asm__ __volatile__("");. Questa è nopun'istruzione che impedisce l'ottimizzazione del ciclo senza comportare costi generali aggiuntivi di una volatilevariabile)
  • Un non allineato void loop()con ottimizzatowhile(1)
  • Un non in linea void loop()con non ottimizzatowhile(1)

Gli schizzi possono essere trovati qui .

Sperimentare

Ho eseguito ciascuno di questi schizzi per 30 secondi, accumulando così 300 punti dati ciascuno . C'era una delaychiamata di 100 millisecondi in ogni loop (senza la quale accadono cose brutte ).

risultati

Ho quindi calcolato i tempi medi di esecuzione di ciascun loop, sottratto 100 millisecondi da ciascuno e quindi tracciato i risultati.

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

Conclusione

  • Un while(1)ciclo non ottimizzato all'interno void loopè più veloce di un compilatore ottimizzato void loop.
  • La differenza di tempo tra il codice non ottimizzato e il codice ottimizzato Arduino predefinito è praticamente insignificante . Farai meglio a compilare manualmente usando avr-gcce usando i tuoi flag di ottimizzazione piuttosto che dipendere dall'IDE di Arduino per aiutarti con esso (se hai bisogno di ottimizzazioni di microsecondi).

NOTA: i valori temporali effettivi non sono significativi qui, la differenza è. I ~ 90 microsecondi di tempo di esecuzione includono una chiamata a Serial.println, microse delay.

NOTA2: ciò è stato fatto utilizzando l'IDE Arduino e i flag predefiniti del compilatore forniti.

NOTA 3: L' analisi ( grafico e calcoli) è stata eseguita utilizzando R.


1
Buon lavoro. Il grafico ha millisecondi non microsecondi ma non è un grosso problema.
Cybergibbons,

@Cybergibbons Questo è abbastanza improbabile dal momento che tutte le misurazioni sono in microsecondi e non ho mai cambiato scala 'ovunque :)
asheeshr
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.