Czy szybsze procesory / zegary mogą wykonywać więcej kodów?


9

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?


.... ATmega328 NIE jest układem ARM. To AVR.
vicatcu

Pozdrawiam, poprawione!
jwbensley

Odpowiedzi:


9

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.).


Cześć, dzięki za twoją pouczającą odpowiedź! Widziałem jeden z nich ( coolcomponents.co.uk/catalog/product_info.php?products_id=808 ), powiedziałeś, że AVR nie może iść szybciej niż 20 MHz, dlaczego tak jest? Układ na powyższej płycie ( uk.farnell.com/stmicroelectronics/stm32f103rbt6/... ) to ARM 72 MHz, czy mogę spodziewać się rozsądnego wzrostu wydajności w sposób opisany powyżej?
jwbensley

2
Podwojenie prędkości przetwarzania może nie zwiększyć przepustowości instrukcji, ponieważ możesz zacząć przekraczać prędkość pobierania instrukcji z pamięci flash. W tym momencie zaczynasz uderzać w „Stany oczekiwania na flash”, w których procesor zatrzymuje się, gdy czeka na instrukcje z pamięci flash. Niektóre mikrokontrolery omijają ten problem, umożliwiając wykonywanie kodu z pamięci RAM, który jest znacznie szybszy niż FLASH.
Majenko,

@Majenko: zabawne, oboje stwierdziliśmy ten sam punkt w tym samym czasie.
Jason S

Zdarza się ... twoje jest lepsze niż moje :)
Majenko,

1
OK, zaznaczyłem odpowiedź Vicatcu jako „odpowiedź”. Wydaje mi się, że było to najbardziej odpowiednie w odniesieniu do mojego pierwotnego pytania dotyczącego szybkości związanej z wydajnością, chociaż wszystkie odpowiedzi są świetne i jestem naprawdę zaskoczony odpowiedziami wszystkich. Pokazali mi, że jest to szerszy temat, niż początkowo zdawałem sobie sprawę, więc wszyscy dużo mnie uczą i dają wiele do badań, więc dziękuję wszystkim: D
jwbensley

8

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) % 4do 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) & 3by 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 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!


dobra rada na temat empirycznych technik pomiaru czasu wykonania
vicatcu

Kolejna świetna odpowiedź na moje pytanie, bardzo dziękuję Jasonowi S za ten niesamowity kawałek wiedzy! Dwie rzeczy widoczne po przeczytaniu tego; Po pierwsze, mogę zwiększyć przerwanie z każdego 100uS do 500uS, aby dać kodowi więcej czasu na wykonanie. Zdaję sobie sprawę, że teraz tak naprawdę nie przynosi mi to korzyści. Po drugie, uważam, że mój kod może być zbyt nieefektywny, z dłuższym czasem przerwania i lepszym kodem wszystko może być w porządku. Stackoverflow jest lepszym miejscem do opublikowania kodu, więc opublikuję go tam i zamieszczę link do niego tutaj, jeśli ktoś chce zajrzeć i przedstawić jakieś zalecenia, wykonaj następujące czynności: D
jwbensley

5

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):

register unsigned int pointer asm("W9");

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.


lol I „jednocześnie” skomentowałem własną odpowiedź na temat strojenia asemblera i przydzielania rejestrów :)
vicatcu

Jeśli wymaga 100us na procesorze 16 MHz - jest oczywiście dość duży, więc to dużo kodu do optymalizacji. Słyszałem, że dzisiejsze kompilatory produkują około 1,1 raza kod niż zestaw zoptymalizowany ręcznie. Zupełnie nie warto tego na tak ogromną rutynę. Do golenia 20% zniżki na funkcję 6-liniową, być może ...
DefenestrationDay

1
Niekoniecznie ... Może to być tylko 5 linii kodu w pętli. I nie chodzi o rozmiar kodu, ale o wydajność kodu . Możesz napisać kod w inny sposób, co przyspieszy jego działanie. Wiem, że robię to z przerwami. Na przykład poświęcając rozmiar dla szybkości. Uruchamiając ten sam kod 10 razy w sekwencji, oszczędzasz czas potrzebny na wykonanie pętli i powiązanych zmiennych licznika. Tak, kod jest 10 razy dłuższy, ale działa szybciej.
Majenko

Cześć Majenko, nie znam montażu, ale myślałem o jego nauce i myślałem, że Arduino będzie mniej skomplikowane niż mój komputer stacjonarny, więc może to być dobry czas na naukę, zwłaszcza, że ​​chcę wiedzieć więcej o tym, co się dzieje i niższy poziom. Jak powiedzieli inni, nie napisałbym tego wszystkiego od nowa, tylko niektórych części. Rozumiem, że mogę wchodzić i wychodzić z ASM w C, czy to prawda, czy w ten sposób można osiągnąć taką mieszankę C i ASM? Zamieszczę post na przepełnieniu stosu dla szczegółów, zaraz po ogólnym pomyśle.
jwbensley

@javano: Tak. Możesz wchodzić i wychodzić z ASM w C. Wiele wbudowanych systemów zostało napisanych w ten sposób - w kombinacji C i asemblera - głównie dlatego, że było kilka rzeczy, których po prostu nie można było zrobić w prymitywnych kompilatorach C dostępnych w czas. Jednak współczesne kompilatory C, takie jak gcc (który jest kompilatorem używanym przez Arduino), teraz obsługują większość, aw wielu przypadkach wszystkie rzeczy, które kiedyś wymagały języka asemblera.
davidcary
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.