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ęść):
Powinniśmy zapomnieć o małej wydajności, powiedzmy około 97% czasu: przedwczesna optymalizacja jest źródłem wszelkiego zła
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:
- funkcje, które są dobrze zawartymi, dobrze zdefiniowanymi małymi procedurami, które raczej się nie zmienią
- funkcje, które mogą wykorzystywać określone instrukcje montażu (min / maks / prawe przesunięcie / itp.)
- funkcje, które są wywoływane wiele razy (dostajesz mnożnik: jeśli zaoszczędzisz 0,5usec na każdym połączeniu i zostanie wywołany 10 razy, to zaoszczędzi ci 5 usec, co jest znaczące w twoim przypadku)
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!