Piszę program do pracy na ATmega 328, który działa z częstotliwością 16 MHz (Arduino Duemilanove, jeśli je znasz, to układ AVR).
Mam proces przerwania uruchomiony co 100 mikrosekund. Powiedziałbym, że nie jest możliwe określenie, ile „kodu” można wykonać w jednej pętli 100 mikrosekund (piszę w C, który prawdopodobnie jest konwertowany na asembler, a następnie na obraz binarny?).
Zależy to również od złożoności kodu (gigantyczna linijka może działać wolniej niż na przykład kilka krótkich linii).
Czy moje rozumowanie jest prawidłowe, ponieważ mój procesor z taktowaniem zegara lub 16 MHz wykonuje 16 milionów cykli na sekundę (oznacza to 16 cykli na mikrosekundę 16 000 000/1 000/1 000); A więc, jeśli chcę zrobić więcej w mojej pętli 100 mikrosekund, kupowanie szybszego modelu, takiego jak wersja 72 MHz, dałoby mi 72 cykle na mikrosekundę (72 000 000/1 000/1 000)?
Obecnie działa trochę za wolno, tj. Zajmuje trochę więcej niż 100 mikrosekund, aby wykonać pętlę (jak długo dokładnie jest zbyt trudne do powiedzenia, ale stopniowo się opóźnia) i chciałbym, aby zrobiło to trochę więcej, jest to rozsądne podejście, które ma szybszy chip, czy oszalałem?
źródło
Odpowiedzi:
Zasadniczo liczba instrukcji montażu, które urządzenie może wykonać na sekundę, będzie zależeć od zestawu instrukcji i liczby cykli, jakie zajmuje każdy typ instrukcji musi wykonać (CPI). Teoretycznie możesz zliczyć swój kod, patrząc na zdemontowany plik asm i szukając funkcji, o którą się martwisz, zliczając wszystkie rodzaje instrukcji w nim zawartych i sprawdzając liczbę cykli z arkusza danych dla docelowego procesora.
Problem określania efektywnej liczby instrukcji na sekundę jest jeszcze bardziej skomplikowany w bardziej złożonych procesorach, ponieważ są one potokowe i mają pamięci podręczne, a co nie. Nie dotyczy to prostego urządzenia, takiego jak ATMega328, które jest pojedynczą instrukcją procesora lotu.
Jeśli chodzi o kwestie praktyczne, w przypadku prostego urządzenia, takiego jak AVR, moja odpowiedź brzmiałaby mniej więcej tak. Podwojenie szybkości zegara powinno zmniejszyć czas wykonywania dowolnej funkcji o połowę. W przypadku AVR nie będą one jednak działać szybciej niż 20 MHz, więc można „podkręcić” Arduino tylko o kolejne 4 MHz.
Ta rada nie uogólnia na procesor, który ma bardziej zaawansowane funkcje. Podwojenie częstotliwości taktowania procesora Intel w praktyce nie podwoi liczby instrukcji wykonywanych na sekundę (z powodu błędnych przewidywań gałęzi, błędów pamięci podręcznej itp.).
źródło
Odpowiedź @ vicatcu jest dość wyczerpująca. Jedną dodatkową rzeczą, na którą należy zwrócić uwagę jest to, że procesor może mieć stan oczekiwania (zablokowane cykle procesora) podczas uzyskiwania dostępu do I / O, w tym pamięci programu i danych.
Na przykład używamy procesora DSP TI F28335; niektóre obszary pamięci RAM są w stanie 0-oczekiwania dla pamięci programu i danych, więc gdy wykonujesz kod w pamięci RAM, działa on w 1 cyklu na instrukcję (z wyjątkiem instrukcji, które trwają dłużej niż 1 cykl). Kiedy wykonujesz kod z pamięci FLASH (wbudowana pamięć EEPROM, mniej więcej), nie może on działać z pełną częstotliwością 150 MHz i jest kilka razy wolniejszy.
Jeśli chodzi o szybki kod przerwania, musisz nauczyć się wielu rzeczy.
Najpierw zapoznaj się ze swoim kompilatorem. Jeśli kompilator wykonuje dobrą robotę, w większości przypadków nie powinien być o wiele wolniejszy niż ręcznie kodowany zestaw. (gdzie „o wiele wolniej”: współczynnik 2 byłby dla mnie OK; współczynnik 10 byłby niedopuszczalny) Musisz dowiedzieć się, jak (i kiedy) korzystać z flag optymalizacji kompilatora i co jakiś czas powinieneś patrzeć na wyjściu kompilatora, aby zobaczyć, jak to działa.
Kilka innych rzeczy, które kompilator może zrobić, aby przyspieszyć kod:
używaj funkcji wbudowanych (nie pamiętam, czy C obsługuje to lub czy jest to tylko C ++ - ism), zarówno dla małych funkcji, jak i dla funkcji, które będą wykonywane tylko raz lub dwa razy. Minusem jest to, że funkcje wbudowane są trudne do debugowania, zwłaszcza jeśli włączona jest optymalizacja kompilatora. Ale oszczędzają ci niepotrzebnych sekwencji wywołań / zwrotów, zwłaszcza jeśli abstrakcja „funkcji” służy raczej do projektowania koncepcyjnego niż do implementacji kodu.
Zajrzyj do podręcznika kompilatora, aby sprawdzić, czy ma on wbudowane funkcje - są to wbudowane funkcje zależne od kompilatora, które odwzorowują bezpośrednio instrukcje montażu procesora; niektóre procesory mają instrukcje montażu, które wykonują użyteczne rzeczy, takie jak odwracanie min / maks / bit, i można to zaoszczędzić czas.
Jeśli wykonujesz obliczenia numeryczne, upewnij się, że nie wywołujesz niepotrzebnie funkcji biblioteki matematycznej. Mieliśmy jeden przypadek, w którym kod był podobny
y = (y+1) % 4
do licznika, który miał okres 4, oczekując, że kompilator zaimplementuje moduł 4 jako bit-AND. Zamiast tego nazywała się biblioteką matematyczną. Zastąpiliśmy więc,y = (y+1) & 3
by robić to, co chcieliśmy.Zapoznaj się ze stroną hakerskich bitów . Gwarantuję, że będziesz używał przynajmniej jednego z nich często.
Powinieneś także używać urządzeń peryferyjnych timera procesora do mierzenia czasu wykonania kodu - większość z nich ma timer / licznik, który można ustawić tak, aby działał z częstotliwością zegara procesora. Zrób kopię licznika na początku i na końcu kodu krytycznego, a zobaczysz, jak długo to potrwa. Jeśli nie możesz tego zrobić, inną alternatywą jest obniżenie pinu wyjściowego na początku kodu i podniesienie go na końcu, a następnie przyjrzenie się temu wynikowi na oscyloskopie w celu pomiaru czasu wykonania. Każde podejście ma swoje kompromisy: wewnętrzny licznik / licznik jest bardziej elastyczny (możesz zmierzyć czas na kilka rzeczy), ale trudniej jest uzyskać informacje, podczas gdy ustawienie / wyczyszczenie pinów wyjściowych jest natychmiast widoczne w zakresie i możesz przechwytywać statystyki, ale trudno jest rozróżnić wiele zdarzeń.
Wreszcie, istnieje bardzo ważna umiejętność związana z doświadczeniem - zarówno ogólna, jak i ze specyficznymi kombinacjami procesorów / kompilatorów: wiedza, kiedy i kiedy nie należy optymalizować . Ogólnie odpowiedź brzmi: nie optymalizuj. Cytat Donalda Knutha jest często publikowany na StackOverflow (zwykle tylko ostatnia część):
Ale jesteś w sytuacji, w której wiesz, że musisz przeprowadzić pewną optymalizację, więc nadszedł czas, aby ugryźć pocisk i zoptymalizować (lub uzyskać szybszy procesor, lub jedno i drugie). Czy NIE pisać całe ISR w montażu. To prawie gwarantowana katastrofa - jeśli to zrobisz, w ciągu miesięcy lub nawet tygodni zapomnisz części tego, co zrobiłeś i dlaczego, a kod prawdopodobnie będzie bardzo kruchy i trudny do zmiany. Prawdopodobnie będą jednak fragmenty twojego kodu, które są dobrymi kandydatami do złożenia.
Znaki wskazujące, że części kodu są odpowiednie do kodowania asemblacji:
Poznaj konwencje wywoływania funkcji kompilatora (np. Gdzie umieszcza argumenty w rejestrach i które rejestry zapisuje / przywraca), abyś mógł pisać procedury asemblowania w języku C.
W moim obecnym projekcie mamy dość dużą bazę kodu z krytycznym kodem, który musi działać w przerwie 10 kHz (100usec - brzmi znajomo?) I nie ma tak wielu funkcji, które są napisane w asemblerze. Są to między innymi obliczenia CRC, kolejki oprogramowania, kompensacja wzmocnienia / przesunięcia ADC.
Powodzenia!
źródło
Należy również zwrócić uwagę na pewną optymalizację, którą można wykonać w celu zwiększenia wydajności kodu.
Na przykład - mam procedurę, która działa z przerwania timera. Procedura musi się zakończyć w ciągu 52µS i musi przejść przez dużą ilość pamięci podczas jej wykonywania.
Udało mi się znacznie zwiększyć prędkość, blokując główną zmienną licznika do rejestru za pomocą (na moim µC i kompilatorze - różnym dla twojego):
Nie znam formatu twojego kompilatora - RTFM, ale będzie coś, co możesz zrobić, aby przyspieszyć procedurę bez konieczności przełączania się na asembler.
Powiedziawszy to, prawdopodobnie lepiej poradzisz sobie z optymalizacją rutyny niż kompilator, więc przejście na asembler może dać ci znaczny wzrost prędkości.
źródło