Część kodu w rdzeniu ATmega, która wykonuje setup () i loop (), jest następująca:
#include <Arduino.h>
int main(void)
{
init();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
Całkiem proste, ale istnieje narzut związany z serialEventRun (); tam.
Porównajmy dwa proste szkice:
void setup()
{
}
volatile uint8_t x;
void loop()
{
x = 1;
}
i
void setup()
{
}
volatile uint8_t x;
void loop()
{
while(true)
{
x = 1;
}
}
X i lotne mają jedynie na celu zapewnienie, że nie jest zoptymalizowane.
W wyprodukowanym ASM otrzymujesz różne wyniki:
Możesz zobaczyć, jak while (prawda) po prostu wykonuje rjmp (skok względny) cofając kilka instrukcji, podczas gdy loop () wykonuje odejmowanie, porównanie i wywołanie. To są 4 instrukcje vs 1 instrukcja.
Aby wygenerować ASM jak wyżej, musisz użyć narzędzia o nazwie avr-objdump. Jest to dołączone do avr-gcc. Lokalizacja różni się w zależności od systemu operacyjnego, więc najłatwiej jest wyszukiwać według nazwy.
avr-objdump może działać na plikach .hex, ale brakuje w nich oryginalnego źródła i komentarzy. Jeśli właśnie zbudowałeś kod, będziesz mieć plik .elf, który zawiera te dane. Ponownie, lokalizacja tych plików różni się w zależności od systemu operacyjnego - najłatwiejszym sposobem ich zlokalizowania jest włączenie pełnej kompilacji w preferencjach i sprawdzenie, gdzie są przechowywane pliki wyjściowe.
Uruchom polecenie w następujący sposób:
avr-objdump -S output.elf> asm.txt
I sprawdź dane wyjściowe w edytorze tekstu.
main.c
używanego przez Arduino IDE. Nie oznacza to jednak, że biblioteka HardwareSerial jest dołączona do szkicu; faktycznie nie jest włączone, jeśli nie go używać (dlatego istniejeif (serialEventRun)
wmain()
funkcji Jeśli nie używać HardwareSerial biblioteka wtedy.serialEventRun
będzie zerowa, stąd nie ma połączenia.Odpowiedź Cybergibbonsa całkiem ładnie opisuje generowanie kodu asemblera i różnice między dwiema technikami. Ma to stanowić uzupełniającą odpowiedź, biorąc pod uwagę kwestię różnic praktycznych , tj. Ile różnicy zrobi każde z nich pod względem czasu realizacji .
Odmiany kodu
Zrobiłem analizę obejmującą następujące odmiany:
void loop()
(który jest wprowadzany podczas kompilacji)void loop()
(za pomocą__attribute__ ((noinline))
)while(1)
(która zostaje zoptymalizowana)while(1)
(przez dodanie__asm__ __volatile__("");
. Jest tonop
instrukcja, która zapobiega optymalizacji pętli bez powodowania dodatkowych narzutówvolatile
zmiennej)void loop()
z optymalizacjąwhile(1)
void loop()
z niezoptymalizowanymwhile(1)
Szkice można znaleźć tutaj .
Eksperyment
Uruchomiłem każdy z tych szkiców przez 30 sekund, gromadząc w ten sposób 300 punktów danych . W
delay
każdej pętli było wywołanie 100 milisekund (bez których zdarzają się złe rzeczy ).Wyniki
Następnie obliczyłem średni czas wykonania każdej pętli, odejmowałem 100 milisekund od każdej, a następnie narysowałem wyniki.
http://raw2.github.com/AsheeshR/Arduino-Loop-Analysis/master/Figures/timeplot.png
Wniosek
while(1)
pętla wewnątrzvoid loop
jest szybsza niż zoptymalizowany kompilatorvoid loop
.avr-gcc
własnych flag optymalizacji i zamiast korzystać z Arduino IDE, aby ci w tym pomóc (jeśli potrzebujesz optymalizacji mikrosekundowych).UWAGA: Rzeczywiste wartości czasu nie mają tutaj znaczenia, różnica między nimi jest. Do ~ 90 mikrosekund czasu wykonania obejmuje wywołanie
Serial.println
,micros
adelay
.UWAGA 2: Dokonano tego przy użyciu Arduino IDE i domyślnych flag kompilatora, które dostarcza.
UWAGA 3: Analiza (wykres i obliczenia) została wykonana przy użyciu R.
źródło