Czy monitorować cykle zegara dla kodu w Arduino / AVR?

11

Czy można monitorować blok kodu i określić liczbę cykli zegara procesora, które kod wziął na procesorze ATMU Arduino i / lub AVR? czy powinienem raczej monitorować mikrosekundy przed i po uruchomieniu kodu? Uwaga: nie interesuje mnie czas rzeczywisty (jak w, ile rzeczywistych sekund minęło), a nie „ile cykli zegara wymaga tego kodu od procesora”

Obecne rozwiązanie, które mogę wymyślić, pochodzi z time.c:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )

wiring.c dodaje:

#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

Za pomocą tego konta mogłem obliczyć cykle zegara przekazane przez monitorowanie minionych mikrosekund, a następnie przekazać je do microsecondsToClockCycles (). Moje pytanie brzmi, czy jest lepszy sposób?

sidenote: czy istnieją dobre zasoby do monitorowania wydajności AVR. Wyszukiwania w witrynie lmgtfy.com i na różnych forach nie przynoszą żadnych oczywistych wyników poza eksploracją liczników czasu

dzięki

cyphunk
źródło

Odpowiedzi:

6

Najprostszą metodą jest spowodowanie, że kod wyciągnie kod PIN, zanim wykona kod, który chcesz odmierzyć, i pociągnie go w dół, gdy skończy cokolwiek robić. Następnie wykonaj pętlę kodu (lub użyj oscyloskopu cyfrowego z pamięcią w trybie pojedynczego strzału) i po prostu ustaw zakres, a następnie przypnij. Długość impulsu mówi, ile czasu zajęło wykonanie fragmentu kodu plus jeden cykl zegara od zmiany stanu pinów (myślę, że zajmuje to jeden cykl, nie jestem w 100% pewien).

Mieszaniec
źródło
Dzięki. Tak, widzę, że jest to prawdopodobnie najdokładniejsze rozwiązanie. Wciąż rzucam się na kod, który dałby mi przynajmniej ogólną analizę wykorzystania cyklu w kodzie. Użyję tego do zbudowania narzędzi do testowania i byłoby miło ustawić górne granice dla parametrów, takich jak maksymalny dozwolony czas działania w oparciu o efektywność kodu + wszystko, co z nim związane, działa na bieżącym procesorze Atmel w use
cyphunk
4

Co rozumiesz przez „monitor”?

Liczenie cykli zegara dla AVR nie powinno być trudne dla małych fragmentów kodu asemblera.

Możesz także ustawić port przed wykonaniem kodu i zresetować go później, a także monitorować go za pomocą analizatora logicznego lub oszilloskopu, aby uzyskać czas.

Możesz też odczytywać czas z szybko działającego timera, jak mówisz.

starblue
źródło
Przez monitor mam na myśli określenie liczby cykli używanych przez kod. coś w stylu (uwaga, formatowanie kodu prawdopodobnie zostanie spłaszczone przez silnik komentarzy): clocks = startCountingAtmegaClocks (); for ... {for ... {digitalRead ...}} Serial.print („liczba wykorzystanych cykli:”); Serial.print (currentCountingAtmegaClocks () - zegary, DEC);
cyphunk
Ale tak, twoja odpowiedź jest taka, jak zakładałem, że są moje opcje. Wydaje mi się, że jeśli potrafię obliczyć cykle zegara, które asembler wziąłby ręcznie, to być może ktoś napisał już jakiś fajny kod, aby to zrobić programowo
cyphunk
3

To jest przykład Arduino używającego funkcji clockCyclesPerMicrosecond () do obliczania upływających zegarów. Ten kod będzie czekać 4 sekundy, a następnie wydrukuje czas, jaki upłynął od uruchomienia programu. Lewe 3 wartości to całkowity czas (mikrosekundy, milisekundy, całkowite cykle zegara), a prawe 3 najbardziej upłynęły czasy:

Wynik:

clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002	64051776	::	4003236	4002	64051760
8006668 8006	128106688	::	4003432	4004	64054912
12010508    12010	192168128	::	4003840	4004	64061440
16014348    16014	256229568	::	4003840	4004	64061440
20018188    20018	320291008	::	4003840	4004	64061440
24022028    24022	384352448	::	4003840	4004	64061440
28026892    28026	448430272	::	4004864	4004	64077824
32030732    32030	512491712	::	4003840	4004	64061440
36034572    36034	576553152	::	4003840	4004	64061440
40038412    40038	640614592	::	4003840	4004	64061440
44042252    44042	704676032	::	4003840	4004	64061440
48046092    48046	768737472	::	4003840	4004	64061440
52050956    52050	832815296	::	4004864	4004	64077824

Jestem pewien, że istnieje uzasadnione wytłumaczenie, dlaczego pierwsze pętle miały krótsze cykle zegara niż większość i dlaczego wszystkie inne pętle przełączają się między dwiema długościami cykli zegara.

Kod:

unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
        Serial.begin(9600);
}
boolean firstloop=1;
void loop() { 
        delay(4000);

        if (firstloop) {
                Serial.print("clocks for 1us:");
                ck=microsecondsToClockCycles(1);
                Serial.println(ck,DEC);
                firstloop--;
                Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
        }

        _us=us;
        _ms=ms;
        _ck=ck;

        us=micros(); // us since program start
        ms=millis();
        //ms=us/1000;
        ck=microsecondsToClockCycles(us);
        Serial.print(us,DEC);
        Serial.print("\t");
        Serial.print(ms,DEC);
        Serial.print("\t");
        Serial.print(ck,DEC);     
        Serial.print("\t::\t");

        __us = us - _us;
        __ms = ms - _ms;
        __ck = ck - _ck;
        Serial.print(__us,DEC);
        Serial.print("\t");
        Serial.print(__ms,DEC);
        Serial.print("\t");
        Serial.println(__ck,DEC);     

}

Uwaga: jeśli usuniesz 4-sekundowe opóźnienie, zaczniesz widzieć efekty Serial.print () znacznie wyraźniej. Uwaga: tutaj porównywane są 2 przebiegi. Uwzględniłem tylko 4 próbki blisko siebie z odpowiednich dzienników.

Uruchom 1:

5000604 5000	80009664	::	2516	2	40256
6001424 6001	96022784	::	2520	3	40320
7002184 7002	112034944	::	2600	3	41600
8001292 8001	128020672	::	2600	3	41600

Uruchom 2:

5002460 5002	80039360	::	2524	3	40384
6000728 6000	96011648	::	2520	2	40320
7001452 7001	112023232	::	2600	3	41600
8000552 8000	128008832	::	2604	3	41664

Upływający czas zwiększa się w stosunku do całkowitego czasu pracy. Po upływie sekundy zegary zwiększają się średnio z 40 tys. Do 44 tys. Dzieje się to konsekwentnie kilka milisekund po 1 sekundzie, a upływające zegary pozostają około 44k przez co najmniej kolejne 10 sekund (nie testowałem tego dalej). Dlatego monitorowanie jest przydatne lub potrzebne. Być może zmniejszona wydajność ma związek z konfiguracją lub błędami szeregowymi? A może kod nie używa pamięci poprawnie i ma wyciek, który wpływa na wydajność itp.

cyphunk
źródło
wiele lat później nadal chciałbym coś, co dokładniej pokazuje zegary za pomocą kodu (w odniesieniu do oscyloskopu). Próbuję ustalić liczbę cykli zegara wymaganych dla digitalWrite () zarówno w 16 MHz, jak i 8 MHZ. W 16 MHZ dostaję 8us / 64clk. Ale w 8 MHZ dostaję 0us / 0clk.
cyphunk
1

Ponieważ każdy wiersz kodu dodany do źródła będzie miał wpływ na wydajność i może zmienić zastosowane optymalizacje. Zmiany powinny stanowić minimum wymagane do wykonania zadania.

Właśnie znalazłem wtyczkę Atmel Studio o nazwie „Debugger pliku z adnotacjami”. http://www.atmel.com/webdoc/aafdebugger/pr01.html Wygląda na to, że przechodzisz przez faktycznie wygenerowany język asemblera, podczas gdy prawdopodobnie żmudne pokaże ci dokładnie, co się dzieje. Być może nadal będziesz musiał zdekodować liczbę cykli potrzebnych dla każdej instrukcji, ale zbliżyłoby się to znacznie bardziej niż niektóre inne opublikowane opcje.

Dla tych, którzy nie wiedzą w folderze Output twojego projektu jest plik z rozszerzeniem LSS. Ten plik zawiera cały oryginalny kod źródłowy w postaci komentarzy, a pod każdym wierszem znajduje się język asemblera, który został wygenerowany na podstawie tego wiersza kodu. Generowanie pliku LSS można wyłączyć, dlatego sprawdź następujące ustawienie.

Właściwości projektu | Toolchain | AVR / GNU Common | OutputFiles

Pole wyboru „.lss (Wygeneruj plik lss)

James
źródło
1

Możesz użyć jednego z wbudowanych timerów. Przygotuj wszystko dla prescaller = 1 i TCNT = 0 przed blokiem. Następnie włącz licznik czasu na linii przed blokiem i wyłącz go na linii po bloku. TCNT będzie teraz przechowywał liczbę cykli, które wziął blok, pomniejszoną o ustalone cykle dla kodu włączania i wyłączania.

Należy zauważyć, że TNCT przepełni się po 65535 cyklach zegara na 16-bitowym zegarze. Możesz użyć flagi przepełnienia, aby podwoić czas działania. Jeśli nadal potrzebujesz dłużej, możesz użyć preskalera, ale uzyska on mniejszą rozdzielczość.

bigjosh
źródło