[avr-gcc] zliczanie modulo

Jan 02, 2007 33 Replies

Alez to jest zle. Chociazby dlatego, ze nie wiesz jak kompilator przetlumaczy to co napisales na assembler. Skad masz gwarancje, ze zapis np. x&0x0f nie zostanie przetlumaczony na x%0x0f? Oczywiscie ten przyklad jest bez sensu, ale w bardziej zlozonych sytuacjach nie masz zadnego przelozenia pomiedzy skladnia w C/C++ a generowanym kodem assemblerowym. Tak wiec jesli naprawde chcesz miec kontrole nad fragmentem kodu to nalezy go napisac w assemblerze, a nie liczyc, ze kompilator dla danej skladni wygeneruje okreslony kod.

Owszem, ale jesli bedzie mozliwe uproszczenie do operacji wycinania bitow to kompilator to zrobi, jesli nie bedzie mozliwe to czlowiek tez z tym nic nie zrobi. IMHO roznica jest wylacznie taka, ze szybciej sie o tym dowiesz.

Nie jest to zwiazane z promowaniem typow char/uchar na typy 16 bitowe, dla ktorych ustawianie i zerowanie pojedynczych bitow nie jest supportowane w assemblerze? Analogiczny problem istnieje w przypadku AVR, mozna go obejsc w sposob maloelegancki, albo elegancko robiac wstawke w assemblerze.

Jezeli masz taka krytyczna sekcje to IMHO jedynym eleganckim rozwiazaniem gwarantujacym otrzymanie oktreslonego kodu jest napisanie go po prostu w assemblerze. Jaka masz gwarancje, ze nowa wersja kompilatora ciagle bedzie sie zachowywac tak samo napotykajac na twoja "sztuczke"?

Piotr Wyderski przemówił ludzkim głosem:

Pokazałem Ci kawałek kodu, w którym to "poradzi" prowadzi do znacznych różnic w długości wykonywania programu (w zależności od procesora oczywiście), co nie zawsze jest pożądane.

Ależ można to rozwiązać (na poziomie kodu C) tak, że długość wykonania będzie z dokładnością do paru instrukcji procesora taka sama, dla każdej stałej (i to wcale nie przez wymuszenie dzielenia). Pokazałem to w innym poście w tym wątku.

Nie, mogę tam wsadzić "blok warunkowy" i uzyskam mniej więcej podobny czas wykonania niezależnie od stałej.

Oczywiście, że ma być zrozumiały. Ale czy to wyklucza pisanie kodu w taki sposób, który daje przy okazji kod lepiej zoptymalizowany?

Rozumiem, że przytoczonym przeze mnie przykład w innym poście zalicza się do kategorii "majsterklepka" ?

T.M.F. przemówił ludzkim głosem:

Człowiek może coś z tym zrobić. Jak? Już to pokazywałem.

Akurat problem o którym pisałem nie był związany z sekcjami krytycznym (choć tu też mógł się objawić), ale z rejestrami obsługi przerwań, na których rozbicie operacji na odczyt-modyfikację-zapis mogło prowadzić do "gubienia" przerwań.

Stosuję jedną podstawową zasadę, niezależnie czy używam "sztuczek", czy też nie: nie zmieniam (wersji) kompilatora w trakcie trwania projektu.

T.M.F. przemówił ludzkim głosem:

Jeśli tylko wartość zmiennej indeks nie przekracza wartości rozmiar przed wykonaniem powyższych instrukcji, to obydwie postaci są funkcjonalnie równoważne.

Owszem, ale kompilator nic nie wie o takim zalozeniu, wiec nie moze zoptymalizowac kodu. Trudno mu z tego czynic zarzut bo musialby czytac w myslach.

Owszem, pokazales zmieniajac funkcjonalnie algorytm rozwiazania. W szczegolnosci jesli trzymamy sie przykladow z modulo i alternatywna operacja logiczna to ten problem nie ma innego rozwiazania.

Czyli miales sekcje krytyczna czasowo. Nakombinowales sie ze sztuczkami, ktorych pewnie nie da sie przeniesc na inny kompilator lub nawet inna wersje tego samego zamiast od razu wrzucic pare instrukcji assemblera majac 100% pewnosci co do uzyskanego kodu i czasu egzekucji.

I rozumiem, ze kazdy projekt zapisujesz z uzytym do jego skompilowania kompilatorem? A co jesli gotowa procedure chcesz przeniesc do innego projektu? Za kazdym razem wszystko piszesz od zera? Sam widzisz, ze powoli dochodzimy do absurdow, bo od poczatku przyjales absurdalne zalozenia. Po to pisze sie w jezyku wysokiego poziomu, zeby korzystac z jego dobrodziejstw. Jesli ma sie krytyczny czasowo/objetosciowo kod to nalezy przejsc do jezyka nizszego poziomu dajacego w zamian pelna kontrole nad generowanym kodem. Inaczej dochodzimy wlasnie do absurdow typu jeden projekt jeden kompilator, brak mozliwosci przeniesienia fragmentow kodu itd.

T.M.F. przemówił ludzkim głosem:

Toć dlatego, od początku, pisałem, że czasem takie "ręczne sterowanie" czasami jest potrzebne i pozwala wygenerowanie lepszego kodu.

Pokaza³e¶ dwa programy, które robi± inne rzeczy. Dla index = rozmiar_tablicy

zwróci 0, a

zwróci 1. Mo¿esz u¿ywaæ ich zamiennie tylko przy scis³ej kontroli zakresu zmiennej index, wiêc nie rozumiem próby "sprzedania" pierwszego jako zamiennika drugiego.

Ale tylko dziêki wiedzy o sposobie zmian parametrów. Spróbuj z tym:

int fn(int x, int y) {

return array[x*y % STALA]; }

No to wsadzaj, do dzie³a. :-)

Przecie¿ nie daje. & i % sprowadzaj± siê do tego samego ci±gu instrukcji.

Ale który z nich? Wrêczanie kompilatora w zamianie % na &? Zdecydowanie tak. Korzystanie z dodatkowej wiedzy o sposobie generowania parametrów? Nie, to siê chwali.

Pozdrawiam Piotr Wyderski

T.M.F. przemówił ludzkim głosem:

Oceniasz to co zrobiłem nie wiedząc nawet jak to zrobiłem i na ile ta "sztuczka" jest przenośna.

Jeżeli to co napiszę w języku wysokiego poziomu spełnia wymagania, to nie widzę powodu, by przenosić to na assembler, tylko dlatego że kiedyś/gdzieś/ktoś może użyć innej wersji kompilatora, albo chcieć przenieść to na inny procesor. Sprowadzasz sprawę do absurdu.

No ale przecie¿ temu nikt nie zaprzecza. Ja ca³y czas odnoszê siê tylko do rêcznej redukcji mocy. Je¶li za¶ chodzi o wersjê z ifem to na architekturach z drogimi skokami warto zastosowaæ wersjê zarytmetyzowan±, eliminuj±c± skok:

#include <stdio.h>

inline unsigned int mod_increment(unsigned int v, const unsigned int m) {

__asm__(" addl $1, %0 \n" \ " cmpl %0, %1 \n" \ " sbbl %1, %1 \n" \ " notl %1 \n" \ " andl %1, %0 \n" \ " \n" : "+r"(v) : "r"(m - 1U) : "cc");

return v; }

inline unsigned int mod_increment_saturated(unsigned int v, const unsigned int m) {

__asm__(" cmpl %1, %0 \n" \ " adcl $0, %0 \n" \ " \n" : "+r"(v) : "r"(m) : "cc");

return v; }

int main(int argc, char *argv[]) {

unsigned int v = 0U;

for(unsigned int i = 0; i < 15; ++i) {

v = mod_increment(v, 3U); printf("%2u: %u\n", i, v); }

v = 0;

for(unsigned int i = 0; i < 15; ++i) {

v = mod_increment_saturated(v, 2U); printf("%2u: %u\n", i, v); }

return 0; }

Piotr Wyderski przemówił ludzkim głosem:

I właśnie o to chodziło. Mam wiedzę, której nie ma kompilator i wykorzystuję ją. Dlaczego nie miałby postąpić podobnie, gdy widzę, że kompilator słabo sobie radzi z pewnymi konstrukcjami? I nie zastąpić ich takimi, które pozwolą wygenerować lepszy kod?

Ale masz gwarancjê, ¿e spe³nia, dan± przez np. standard jêzyka? Je¶li tak, to w porz±dku.

Inny procesor to niekoniecznie, ale wi±zanie siê z okre¶lon± wersj± kompilatora to ju¿ ciekawe podej¶cie.

Ale¿ nie, ma ca³kowit± racjê. Projekt, nad którym ja pracujê ma i wstawki asemblerowe, i wykorzystuje rozszerzenia kompilatorów, a jednak siê te kilka milionów linii kodu ¼ród³owego bez problemu przenosi na kilka zupe³nie ró¿nych rodzin procesorów, kompiluje na 4 seriach kompilatorów, w tym GCC, (w ka¿dej serii jest kilka wersji). Szczerze mówi±c, to nawet by mi do g³owy nie przysz³o wi±zanie siê z okre¶lonym kompilatorem, bo jaki¶ efekt uboczny generuje po¿±dany kod.

Pozdrawiam Piotr Wyderski

Ale to nie kompilator sobie s³abo radzi, tylko Ty zmieni³es algorytm obliczania wyniku. Zadaniem kompilatora jest prze³o¿enie podanego algorytmu na jêzyk maszynowy, a nie wymy¶lanie lepszych algorytmów.

Pozdrawiam Piotr Wyderski

Piotr Wyderski napisał(a):

Heh, pewnie nic nigdy nie pisałeś w C na texasowe DSP. Do większości rodzin nie istnieje gcc więc projektant jest zdany tylko na standardowy texasowy kompilator. A platforma sprzętowa - istne cudo. :-& Aby napisać coś przenośnego (co pójdzie i w DSPku, i np. na ARMie), trzeba nieźle główkować w wielu przypadkach. Na przykład TMS320VC5416 ma architekturę

16-bitową. Więc bajt ma 16 bitów, słowo ma 16 bitów, pamięć jest podzielona na słowa 16-bitowe. Sizeof zwraca oczywiście wynik w słowach 16-bitowych. Pamięci danych i programu rozdzielone. Naprawdę trzeba od razu o DSPku myśleć pisząc program, bo potem jest za późno i kod wygodnie pisany na ARMie ciężko przenosi się na tą konkretną rodzinę TI. Inna sprawa, że procesory sygnałowe do języka C w ogóle nie zostały stworzone, ale czasem trzeba go użyć by super zoptymalizowane funkcje asemblerowe jakoś zgrabnie posklejać.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required