Wäre eine Endlosschleife in loop () schneller?

19

Wenn Sie eine typische Skizze schreiben, verlassen Sie sich normalerweise darauf loop(), wiederholt aufgerufen zu werden, solange das Arduino ausgeführt wird. Das Ein- und Aussteigen aus der loop()Funktion erfordert jedoch einen geringen Mehraufwand.

Um dies zu vermeiden, könnten Sie vermutlich eine eigene Endlosschleife erstellen:

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

Ist das ein praktikabler Weg, um die Leistung zu verbessern? Verursacht es andere Probleme, wenn es loop()nie wiederkommt?

Peter Bloomfield
quelle

Antworten:

18

Der Teil des Codes auf einem ATmega-Core, der setup () und loop () ausführt, lautet wie folgt:

#include <Arduino.h>

int main(void)
{
        init();

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

        setup();

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

        return 0;
}

Ziemlich einfach, aber es gibt den Overhead von serialEventRun (); da drin.

Vergleichen wir zwei einfache Skizzen:

void setup()
{

}

volatile uint8_t x;

void loop()
{

    x = 1;

}

und

void setup()
{

}

volatile uint8_t x;

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

Das x und volatile soll nur sicherstellen, dass es nicht optimiert wird.

In der produzierten ASM erhalten Sie unterschiedliche Ergebnisse: Vergleich von zwei

Sie können sehen, dass while (true) nur einen relativen Rücksprung (rjmp) durchführt, während loop () eine Subtraktion, einen Vergleich und einen Aufruf durchführt. Dies ist 4 Anweisungen vs 1 Anweisung.

Um ASM wie oben zu generieren, müssen Sie ein Tool namens avr-objdump verwenden. Dies ist in avr-gcc enthalten. Der Speicherort variiert je nach Betriebssystem, daher ist es am einfachsten, nach Namen zu suchen.

avr-objdump kann mit .hex-Dateien arbeiten, denen jedoch die ursprüngliche Quelle und die Kommentare fehlen. Wenn Sie gerade Code erstellt haben, verfügen Sie über eine .elf-Datei, die diese Daten enthält. Auch hier variiert der Speicherort dieser Dateien je nach Betriebssystem. Am einfachsten ist es, die ausführliche Kompilierung in den Einstellungen zu aktivieren und zu überprüfen, wo die Ausgabedateien gespeichert werden.

Führen Sie den Befehl wie folgt aus:

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

Und untersuchen Sie die Ausgabe in einem Texteditor.

Cybergibbons
quelle
OK, aber gibt es keinen Grund, die Funktion serialEventRun () aufzurufen? Wofür ist das?
jfpoilpret
1
Es ist Teil der von HardwareSerial verwendeten Funktionalität, nicht sicher, warum es nicht entfernt wird, wenn Serial nicht benötigt wird.
Cybergibbons
2
Es wäre hilfreich, kurz zu erläutern, wie Sie die ASM-Ausgabe generiert haben, damit die Benutzer sich selbst überprüfen können.
jippie
@ Cybergibbons wird nie herausgenommen, da es Teil des main.cvon Arduino IDE verwendeten Standards ist . Dies bedeutet jedoch nicht, dass die HardwareSerial-Bibliothek in Ihrer Skizze enthalten ist. eigentlich ist es nicht enthalten , wenn Sie es nicht verwenden (das ist , warum gibt es if (serialEventRun)in main()Funktion Wenn Sie nicht HardwareSerial Bibliothek verwenden dann. serialEventRunnull sein wird, damit kein Anruf.
jfpoilpret
1
Ja, es ist Teil von main.c, wie angegeben, aber ich würde erwarten, dass es optimiert wird, wenn es nicht benötigt wird, daher denke ich, dass Aspekte von Serial immer enthalten sind. Ich schreibe häufig Code, der niemals von loop () zurückkehrt, und stelle keine Probleme mit Serial fest.
Cybergibbons
6

Die Antwort von Cybergibbons beschreibt die Assembler-Code-Generierung und die Unterschiede zwischen den beiden Techniken recht gut. Dies soll eine ergänzende Antwort sein, die sich mit dem Problem in Bezug auf praktische Unterschiede befasst, dh wie viel Unterschied beide Ansätze in Bezug auf die Ausführungszeit bewirken .


Code-Variationen

Ich habe eine Analyse mit folgenden Variationen durchgeführt:

  • Basic void loop()(wird beim Kompilieren eingeblendet)
  • Nicht inliniert void loop()(mit __attribute__ ((noinline)))
  • Schleife mit while(1)(was optimiert wird)
  • Schleife mit nicht optimiert while(1)(durch Hinzufügen __asm__ __volatile__("");. Dies ist eine nopAnweisung, die die Optimierung der Schleife verhindert, ohne dass dies zu einem zusätzlichen Overhead einer volatileVariablen führt.)
  • Ein nicht void loop()mit optimierten Inlinernwhile(1)
  • Ein Un-Inliner void loop()mit einem Un-Optimiertenwhile(1)

Die Skizzen finden Sie hier .

Experiment

Ich habe jede dieser Skizzen 30 Sekunden lang ausgeführt und dabei jeweils 300 Datenpunkte gesammelt . delayIn jeder Schleife gab es einen 100-Millisekunden- Aufruf (ohne den schlechte Dinge passieren ).

Ergebnisse

Ich berechnete dann die mittleren Ausführungszeiten jeder Schleife, subtrahierte jeweils 100 Millisekunden und zeichnete dann die Ergebnisse auf.

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

Fazit

  • Eine nicht optimierte while(1)Schleife void loopist schneller als eine vom Compiler optimierte void loop.
  • Der Zeitunterschied zwischen dem nicht optimierten Code und dem für Arduino optimierten Standardcode ist praktisch unbedeutend . Es ist besser, avr-gccwenn Sie manuell mithilfe Ihrer eigenen Optimierungsflags kompilieren und diese verwenden, als wenn Sie von der Arduino IDE abhängig sind, um dies zu unterstützen (wenn Sie Mikrosekundenoptimierungen benötigen).

HINWEIS: Die tatsächlichen Zeitwerte sind hier nicht von Bedeutung, der Unterschied zwischen ihnen ist. Die ~ 90 Mikrosekunden der Ausführungszeit enthält einen Aufruf an Serial.println, microsund delay.

HINWEIS2: Dies wurde mit der Arduino IDE und den von ihr bereitgestellten Standard-Compiler-Flags durchgeführt.

ANMERKUNG 3: Die Analyse ( grafische Darstellung und Berechnungen) wurde unter Verwendung von R durchgeführt.

Ascheshr
quelle
1
Gute Arbeit. Graph hat Millisekunden, keine Mikrosekunden, aber kein großes Problem.
Cybergibbons
@ Cybergibbons Das ist ziemlich unwahrscheinlich, da alle Messungen in Mikrosekunden sind und ich die Skalen nirgendwo geändert habe :)
asheeshr