Goto page Previous 1, 2, 3, 4, 5 Next
T.M.F.
Guest
Thu Jun 11, 2009 8:40 pm
W dniu 11.06.2009 10:16, Grzegorz Kurczyk pisze:
Quote:
Dziękuję wszystkim za odzew :-)
Metody opisywane przez Kolegów męczyłem już wcześniej (poza przesiadką
na C++), ale nie przynoszą one spodziewanego rezultatu. Może sprecyzuję
o co mi chodzi. Sprawa jest czysto "akademicka" i wynika z mojego
pewnego rodzaju "zboczenia" w dążeniu do absurdalnej optymalizacji kodu
wynikowego
Jest tak. Funkcja zdefiniowana tradycyjnie:
void ProgressBar(char x, char y, char w, char h, char value) {
....
}
przy wywołaniu:
ProgressBar(0, 90, 128, 5, y);
otrzymujemy w kodzie wynikowym:
37e2: 0b 2d mov r16, r11
37e4: 25 e0 ldi r18, 0x05 ; 5
37e6: 40 e8 ldi r20, 0x80 ; 128
37e8: 6a e5 ldi r22, 0x5A ; 90
37ea: 80 e0 ldi r24, 0x00 ; 0
37ec: 0e 94 b4 17 call 0x2f68 ; 0x2f68 <ProgressBar
I to co mnie "wkurza", to czemu łachudra przekazuje parametry w
rejestrach r16, r18, r20, r22, r24 niejako promując typ char do int ?
Jakby nie mógł po kolei r16..r20.
Jakiej wersji gcc uzywasz? Ja tu nigdzie nie widze promocji do int bo
starsze czesci tych rejestrow nie zawieraja zera.
Quote:
Oczywiście w tym przypadku nie ma to większego znaczenia, ale przy
większej ilości parametrów przekazywanych do funkcji i/lub większej
ilości zmiennych lokalnych funkcji, zaczyna się kombinacja ze stosem lub
z dolnymi rejestrami. Kompilator w pewnym sensie "szatkuje" sobie obszar
rejestrów doprowadzając do sytuacji, że w pewnym momencie brakuje np
czterech kolejnych rejestrów do zapamiętania lokalnej zmiennej typu long
choć pojedynczych wolnych rejestrów jest wystarczająca ilość.
Sprawdz jak to sie zachowa przy wiekszej ilosci parametrow. Zapewne
kompilator bedzie oszczedniej gospodarowal rejestrami. Zauwaz, ze w
twoim przykladzie nie ma takiej potrzeby, a wygenerowany kod jest tak
samo efektywny.
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
T.M.F.
Guest
Thu Jun 11, 2009 8:40 pm
Quote:
void Rysuj(tRect&t);
Inaczej kompilator musi utworzyc kopie obiektu tRect i ta kopie dopiero
przekazac do funkcji.
W C++ moze to byc faktycznie kosztowne.
W C tez powinno byc kosztowne. Bo skad kompilator ma wiedziec, ze Rysuj
nie modyfikuje struktury tRect?
Nie musi wiedziec. Ma wrzucic cala na stos, co powinno pojsc dosc
szybko.
Tak sie nie da. Jesli tRect jest gdzies dalej wykorzystywany to
kompilator musi utworzyc jego kopie, zeby zagwarantowac, ze Rysuj jej
nie zmodyfikuje - to wynika ze standardu. Oczywiscie optymalizator moze
zauwazyc, ze nasze tRect jest dalej niewykorzystywane i z tego etapu
zrezygnowac - no ale to juz zaklada, ze optymalizator jest dosc
sensowny. W tym przypadku zapewne sobie poradzi. Jesli przekazesz adres
struktury bedzie to zawsze dzialac jak nalezy.
Quote:
W pozostalych sytuacjach trzeba by spojrzec w kod wynikowy co lepiej
kompilatorowi wyszlo.
A wracajac do meritum .. wychodzi na to ze najlepiej byloby odwrocic
sprawe - zrobic funcje z 4 parametrami, a nad nia ewentualnie
nadbudowac wersje ze struktura.
I nie korzystac z niej bez potrzeby
Niekoniecznie. 4 parametry to w idealnym przypadku 4 8-bitowe rejestry.
Zazwyczaj wiaze sie to z ich wczesniejszym odlozeniem na stosie i potem
ponownym pobraniem. Przy przekazaniu przez wskazanie mamy tylko dwa
8-bitowe rejestry wskazujace na strukture, co wiaze sie zmniejszym
nakladem na przekazanie parametrow. W procedurze czesto jest to
optymalizowane jako LD Rx,Z+y, lub podobne.
Sa to pewne zalozenia i wymagaja odpowiedniego procka.
W wielu moze wyjsc odwrotnie.
Ale mowimy konkretnie o AVR i AVR-gcc.
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
T.M.F.
Guest
Thu Jun 11, 2009 8:40 pm
Quote:
ale czasem tak dla sportu zaglądam do pliku .lss i patrzę co tam
kompilator wysmarował.
Moja rada - nie zaglądać. Ja co zajrzę to rzucam mięsem (a bo to brak
optymalizacji przy adresowaniu tablic struktur, albo przy pobieraniu
wielu stałych z flasha, albo przy alokacji ramki na stosie przy
przekazywaniu parametrów itd.)
A ja ostatnio sporo dziubię na ARMa w gcc i przeglądając listing
asemblerowy co chwila dziwię się, jak to optymalizator ładnie
wykoncypował, że sam bym lepiej w asemblerze nie napisał. Tak że jedyna
rada - zmienić platformę.
Cos Zbych przesadza. Tak sobie patrze w swoje programy i tez na AVR gcc
ladnie optymalizuje. Czasami pobieznie patrzac na kod wydaje sie, ze
mozna by cos zrobic lepiej, ale przy dokladniejszym spojrzeniu okazuje
sie, ze jednak nie.
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
Adam Dybkowski
Guest
Thu Jun 11, 2009 9:16 pm
Zbych pisze:
Quote:
A ja ostatnio sporo dziubię na ARMa w gcc i przeglądając listing
asemblerowy co chwila dziwię się, jak to optymalizator ładnie
wykoncypował, że sam bym lepiej w asemblerze nie napisał. Tak że jedyna
rada - zmienić platformę.
To pokaż mi jeszcze ARMa, który po zatrzymaniu zegara pobiera < 1uA.
A cóż to za wymaganie? 1uA to pobiera dobry RTC a nie mikrokontroler.
AVRy mają prąd upływu do 1uA na każdy pin I/O (patrzę w PDFa pierwszej z
brzegu ATmegi 128) plus dodatkowo typowo 5uA (max. 10uA) w najgłębszym
power-down i to przy wyłączonym watchdogu. Oprócz tego każdy obciążony
pull-up zjada nieco prądu (typowa rezystancja to 20-50 kOhm). Daleko
stąd raczej do magicznego 1uA.
Jak chcesz oszczędzać prąd to bierz się raczej za rodzinę MSP430 a nie AVRy.
--
Adam Dybkowski
http://dybkowski.net/
Uwaga: przed wysłaniem do mnie maila usuń cyfry z adresu.
T.M.F.
Guest
Thu Jun 11, 2009 10:40 pm
Quote:
AVRy mają prąd upływu do 1uA na każdy pin I/O (patrzę w PDFa pierwszej z
brzegu ATmegi 128) plus dodatkowo typowo 5uA (max. 10uA) w najgłębszym
power-down i to przy wyłączonym watchdogu. Oprócz tego każdy obciążony
pull-up zjada nieco prądu (typowa rezystancja to 20-50 kOhm). Daleko
stąd raczej do magicznego 1uA.
Zobacz np. ATMega48PA/88PA/168PA/328PA. W uspieniu prad 0,8 mikroA. Przy
normalnym dzialaniu z zegarem 20MHz prad 2-3mA. ARMy sie do tego nawet
nie zblizaja.
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
T.M.F.
Guest
Thu Jun 11, 2009 11:40 pm
W dniu 11.06.2009 15:02, Zbych pisze:
Quote:
T.M.F. pisze:
ale czasem tak dla sportu zaglądam do pliku .lss i patrzę co tam
kompilator wysmarował.
Moja rada - nie zaglądać. Ja co zajrzę to rzucam mięsem (a bo to brak
optymalizacji przy adresowaniu tablic struktur, albo przy pobieraniu
wielu stałych z flasha, albo przy alokacji ramki na stosie przy
przekazywaniu parametrów itd.)
A ja ostatnio sporo dziubię na ARMa w gcc i przeglądając listing
asemblerowy co chwila dziwię się, jak to optymalizator ładnie
wykoncypował, że sam bym lepiej w asemblerze nie napisał. Tak że jedyna
rada - zmienić platformę.
Cos Zbych przesadza.
Nie przesadza, wystarczy sprawdzić.
No zagladam dosyc czesto przy okazji debuggowania tego co napisalem.
Jakis koszmarnych brakow optymalizacji nie zauwazylem. Mozesz podac
konkretny przyklad w C, ktory jest zle optymalizowany? To zawsze mozna
zglosic jako bug.
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
Zbych
Guest
Fri Jun 12, 2009 8:54 am
T.M.F. pisze:
Quote:
No zagladam dosyc czesto przy okazji debuggowania tego co napisalem.
Jakis koszmarnych brakow optymalizacji nie zauwazylem. Mozesz podac
konkretny przyklad w C, ktory jest zle optymalizowany? To zawsze mozna
zglosic jako bug.
To zależy co kto uważa za koszmarne. Poniżej parę przykładów, ale
wątpię, żeby komuś chciało się to czytać.
Wszystkie przykłady były optymalizowane na rozmiar.
---------------------------------------------------------
Testowanie warunków logicznych:
#define PIN1_IS_LOW (!(PINB & (1<<PINB7)))
#define PIN2_IS_LOW (!(PINB & (1<<PINB6)))
volatile uint8_t a;
asm volatile(";start");
a = (PIN1_IS_LOW || PIN2_IS_LOW);
asm volatile(";stop");
Kod wynikowy:
32 ;start
33 ; 0 "" 2
34 .LSM2:
35 /* #NOAPP */
36 000a 1F99 sbic 35-32,7
37 000c 00C0 rjmp .L2
38 000e 81E0 ldi r24,lo8(1)
39 0010 90E0 ldi r25,hi8(1)
40 0012 00C0 rjmp .L3
41 .L2:
42 0014 83B1 in r24,35-32
43 0016 90E0 ldi r25,lo8(0)
44 0018 26E0 ldi r18,6
45 001a 9695 1: lsr r25
46 001c 8795 ror r24
47 001e 2A95 dec r18
48 0020 01F4 brne 1b
49 0022 8095 com r24
50 0024 9095 com r25
51 0026 8170 andi r24,lo8(1)
52 0028 9070 andi r25,hi8(1)
53 .L3:
54 002a 8983 std Y+1,r24
55 .LVL0:
56 .LSM3:
57 /* #APP */
58 ; 96 "main.c" 1
59 ;stop
Pomijając już tą nieszczęsną promocję do inta, to najbardziej w tym
kodzie rozwaliło mnie wyłuskiwanie 6 bitu.
Delikatna modyfikacja kodu:
if (PIN1_IS_LOW || PIN2_IS_LOW) a = true;
else a = false;
daje już coś takiego:
62 /* #NOAPP */
63 002c 1F9B sbis 35-32,7
64 002e 00C0 rjmp .L4
65 0030 1E99 sbic 35-32,6
66 0032 00C0 rjmp .L5
67 .L4:
68 0034 81E0 ldi r24,lo8(1)
69 0036 8983 std Y+1,r24
70 .LVL1:
71 0038 00C0 rjmp .L6
72 .L5:
73 .LSM5:
74 003a 1982 std Y+1,__zero_reg__
75 .LVL2:
76 .L6:
----------------------------------------------------------
Przygotowanie ramki na stosie dla 5 parametrów 16-bitowych:
1637 0068 ADB7 in r26,__SP_L__
1638 006a BEB7 in r27,__SP_H__
1639 006c 1A97 sbiw r26,10
1640 006e 0FB6 in __tmp_reg__,__SREG__
1641 0070 F894 cli
1642 0072 BEBF out __SP_H__,r27
1643 0074 0FBE out __SREG__,__tmp_reg__
1644 0076 ADBF out __SP_L__,r26
Przy dwóch parametrach:
1751 010e 00D0 rcall .
1752 0110 00D0 rcall .
Łatwo policzyć, że poniżej 8 parametrów bardziej opłaca się wersja z
rcall (program optymalizowany na rozmiar).
-----------------------------------------------------------
Adresowanie struktur i pobieranie adresów z flasha:
typedef struct{
const prog_char * a;
const prog_char * b;
const prog_char * c;
const prog_char * d;
}Aqq;
prog_char s[] = "Aqq";
Aqq PROGMEM tab[]={
{s,s,s,s},
{s,s,s,s},
{s,s,s,s},
{s,s,s,s},
};
asm volatile (";start");
for (uint8_t i = 0; i < tab_size(tab); i++){
blabla( pgm_read_word( &tab[i].a ), pgm_read_word( &tab[i].b ),
pgm_read_word( &tab[i].c ), pgm_read_word( &tab[i].d ) );
}
asm volatile (";stop");
Pierwszy problem to wyliczanie adresu w flashu:
69 .LBB24:
70 0024 F901 movw r30,r18
71 .LVL4:
72 0026 E050 subi r30,lo8(-(tab+4))
73 0028 F040 sbci r31,hi8(-(tab+4))
74 /* #APP */
75 ; 106 "main.c" 1
76 002a 4591 lpm r20, Z+
77 002c 5491 lpm r21, Z
78
79 ; 0 "" 2
Pomimo, że jest (czy raczej mogłoby być) użyte adresowanie z
postinkrementacją, to adresy kolejnych elementów są i tak przed każdym
pobraniem na nowo wyliczane. W przykładzie, który podałem daje to 12
niepotrzebnych instrukcji w każdym obiegu tej krótkiej pętli i dodatkowo
zajętą parę rejestrów R19:R18.
Drugi problem, to wyliczanie od początku adresu komórki w tablicy przy
każdym obiegu pętli:
27 ;start
28 ; 0 "" 2
29 /* #NOAPP */
30 0000 C0E0 ldi r28,lo8(0)
31 0002 D0E0 ldi r29,hi8(0)
32 .L2:
33 .LBB21:
34 .LBB22:
35 .LSM2:
36 0004 9E01 movw r18,r28
37 0006 83E0 ldi r24,3
38 0008 220F 1: lsl r18
39 000a 331F rol r19
40 000c 8A95 dec r24
41 000e 01F4 brne 1b
42 0010 F901 movw r30,r18
43 .LVL0:
[...]
96 .LBE25:
97 0038 0E94 0000 call blabla
98 .LVL8:
99 003c 2196 adiw r28,1
100 .LSM3:
101 003e C430 cpi r28,4
102 0040 D105 cpc r29,__zero_reg__
103 0042 01F4 brne .L2
104 .LBE21:
105 .LSM4:
106 /* #APP */
107 ; 108 "main.c" 1
108 ;stop
Wystarczy prosty myk ze wskaźnikiem i pętla się skraca:
asm volatile (";start");
for (uint8_t i = 0; i < tab_size(tab); i++){
const Aqq * p = &tab[i];
blabla( pgm_read_word( &p->a ), pgm_read_word( &p->b ), pgm_read_word(
&p->c ), pgm_read_word( &p->d ) );
}
asm volatile (";stop");
27 ;start
28 ; 0 "" 2
29 /* #NOAPP */
30 0000 C0E0 ldi r28,lo8(tab)
31 0002 D0E0 ldi r29,hi8(tab)
[...]
87 0022 0E94 0000 call blabla
88 .LVL8:
89 0026 2896 adiw r28,8
90 .LBE23:
91 .LSM3:
92 0028 80E0 ldi r24,hi8(tab+32)
93 002a C030 cpi r28,lo8(tab+32)
94 002c D807 cpc r29,r24
95 002e 01F4 brne .L2
96 .LBE22:
97 .LSM4:
98 /* #APP */
99 ; 109 "main.c" 1
100 ;stop
1
A podobno gcc ma super optymalizator do pętli (widocznie moja wersja
gdzieś go zgubiła).
Zbych
Guest
Fri Jun 12, 2009 9:14 am
Adam Dybkowski pisze:
Quote:
Zbych pisze:
A ja ostatnio sporo dziubię na ARMa w gcc i przeglądając listing
asemblerowy co chwila dziwię się, jak to optymalizator ładnie
wykoncypował, że sam bym lepiej w asemblerze nie napisał. Tak że jedyna
rada - zmienić platformę.
To pokaż mi jeszcze ARMa, który po zatrzymaniu zegara pobiera < 1uA.
A cóż to za wymaganie? 1uA to pobiera dobry RTC a nie mikrokontroler.
To chyba nie jest wygórowane wymaganie dla _wyłączonego_ uC (ale
potrafiącego się wybudzić po zmianie stanu portów)? STM32 prawie by się
łapał, gdy nie to, że prąd w nim rośnie ze wzrostem temperatury.
Quote:
AVRy mają prąd upływu do 1uA na każdy pin I/O (patrzę w PDFa pierwszej z
brzegu ATmegi 128) plus dodatkowo typowo 5uA (max. 10uA) w najgłębszym
power-down i to przy wyłączonym watchdogu. Oprócz tego każdy obciążony
pull-up zjada nieco prądu (typowa rezystancja to 20-50 kOhm). Daleko
stąd raczej do magicznego 1uA.
Jak chcesz oszczędzać prąd to bierz się raczej za rodzinę MSP430 a nie AVRy.
Spokojnie, nie tylko TI robi takie wynalazki. Atmel ma uC "pico-power",
Microchip "nono-watt".
Poza tym za 10zł dostaniesz najmniejszego MSP430 (4kB flash, 256B Ram).
Za podobne pieniądze mam ATmega324p, która spełnia moje wymagania co do
poboru prądu (i ma potrzebne peryferia). Zresztą jaką mam gwarancję, że
gcc będzie generowało lepszy kod na msp430 niż na avr?
(pamiętasz? polecałeś ARMa ze względu na lepsze wsparcie w gcc). Ruch na
liście mailingowej msp430-gcc jest raczej znikomy. Kolejna rzecz, to
zakres napięć zasilających. AVR mogę zasilić prosto z li-ion. Do MSP430
musiałbym dodać stabilizator (który też żre prąd na swoje potrzeby).
Grzegorz Kurczyk
Guest
Fri Jun 12, 2009 10:05 am
Użytkownik Zbych napisał:
Quote:
T.M.F. pisze:
No zagladam dosyc czesto przy okazji debuggowania tego co napisalem.
Jakis koszmarnych brakow optymalizacji nie zauwazylem. Mozesz podac
konkretny przyklad w C, ktory jest zle optymalizowany? To zawsze mozna
zglosic jako bug.
To zależy co kto uważa za koszmarne. Poniżej parę przykładów, ale
wątpię, żeby komuś chciało się to czytać.
Wszystkie przykłady były optymalizowane na rozmiar.
---------------------------------------------------------
Testowanie warunków logicznych:
#define PIN1_IS_LOW (!(PINB & (1<<PINB7)))
#define PIN2_IS_LOW (!(PINB & (1<<PINB6)))
volatile uint8_t a;
asm volatile(";start");
a = (PIN1_IS_LOW || PIN2_IS_LOW);
asm volatile(";stop");
Ooooo to, to

W programach unikam operacji logicznych na bitach
portów, bo wychodzą koszmarki. Czasem zamiast:
if((PINB & (1<<7)) && (PINC & (1<<3))) {...}
lepiej napisać
if(PINB & (1<<7))
if(PINC & (1<<3))
{...}
Z jakiś troszkę wcześniejszych wersji pamiętam, że samo wstawienie
operatora ! na bicie portu automatycznie generowało koszmarek z rozkazem
in i wyliczaniem stanu bitu na piechotę.
if(PINB & (1<<7)) {...} wyglądało poprawnie z użyciem sbic/sbis,
ale już:
if(!(PINB & (1<<7))) {...} generowało wspomniany przez Kolegę koszmarek
i w zastępstwie używałem:
if(PINB & (1<<7)) else {...} i juz było cacy na sbic/sbis
Co do adresowania tablic w pamięci FLASH w pętli to już od dawna stosuję
wyłącznie inkrementację wskaźnika, bo te każdorazowe wyliczanie adresu
też mnie biło w oczy :-)
P.S. Używam trochę starawej wersji 20070525, bo sprawia mi najmniej
problemów. Późniejsze wersje 2008xxxx sprawiły mi zbyt dużo psikusów i
trochę się zraziłem. Choć może warto wypróbować jakąś świeżynkę :-)
Pozdrawiam
Grzegorz
Zbych
Guest
Fri Jun 12, 2009 11:29 am
T.M.F. pisze:
Quote:
Pomijając już tą nieszczęsną promocję do inta, to najbardziej w tym
kodzie rozwaliło mnie wyłuskiwanie 6 bitu.
Promocja do int wynika ze standardu C wiec trudno tu robić zarzut
kompilatorowi. Żeby to ominąć wystarczy explicite typecastowac.
Wyraźnie napisałem, że nie promocja jest tu największym problemem.
Zresztą rzutowanie i tak tego nie poprawia.
Quote:
Łatwo policzyć, że poniżej 8 parametrów bardziej opłaca się wersja z
rcall (program optymalizowany na rozmiar).
Tu sprawa nie jest tak oczywista. Co prawda rcall są krótsze w sensie
długości kodu, ale zajmuja 3 takty zegara.
Specjalnie napisałem, że optymalizacja była ustawiona na rozmiar, więc
rcall powinno mieć pierwszeństwo.
Quote:
To zalezy zapewne od zdefiniowanej przez ciebie funkcji tab_size(tab).
Kompilator za każdym obiegiem petli musi ja wywołać, żeby wilczyć wynik
dla danego argumentu dlaczego? Bo ja źle zdefiniowałeś.
Zapomniałem wstawić. Oto kod:
#define tab_size(__a) (sizeof(__a)/sizeof(__a[0]))
Jeśli według ciebie jest źle zdefiniowana, to czekam na poprawki.
Quote:
Sprawdziłem to u siebie i nic takiego się nie dzieje. Musiałem
zdefiniować dodatkowo dziwna funkcje Babla bo mi kompilator to
optymalizował i ją wywalał.
Ja na potrzeby przykładu w ogóle jej nie zdefiniowałem (nie jest to
potrzebne, disasemblację zrobiłem po kompilacji, bez linkowania).
Quote:
A podobno gcc ma super optymalizator do pętli (widocznie moja wersja
gdzieś go zgubiła).
Jak ci nie pasuje gcc to zaproponuj cos lepszego...
Chciałeś przykłady, to ci je pokazałem. Nie jest w tym przypadku istotne
czy inne kompilatory zrobiłyby to lepiej (a myślę, że IAR byłby lepszy).
Quote:
Jak ci nie pasuje gcc to zaproponuj cos lepszego... nie mowiac juz o
tym, ze masz kod źródłowy wiec możesz się wykazać dla potomności
Dzięki, już kiedyś próbowałem (na innym porcie gcc). To nie jest robota
na 1 dzień, a ja mam swoją pracę.
Zbych
Guest
Fri Jun 12, 2009 12:27 pm
T.M.F. pisze:
Quote:
Sprawdziłem to u siebie i nic takiego się nie dzieje. Musiałem
zdefiniować dodatkowo dziwna funkcje Babla bo mi kompilator to
optymalizował i ją wywalał.
Ja na potrzeby przykładu w ogóle jej nie zdefiniowałem (nie jest to
potrzebne, disasemblację zrobiłem po kompilacji, bez linkowania).
To wtedy petla sie totalnie zoptymalizowala i wywalilo mi te wywolania w
ogole.
Eeee, coś musiałeś skopać. Jak kompilator może zoptymalizować coś czego
nie widzi?
cały przykład powinien wyglądać tak:
#define tab_size(__a) (sizeof(__a)/sizeof(__a[0]))
void blabla( uint16_t, uint16_t, uint16_t, uint16_t);
typedef struct{
const prog_char * a;
const prog_char * b;
const prog_char * c;
const prog_char * d;
}Aqq;
prog_char s[] = "Aqq";
Aqq PROGMEM tab[]={
{s,s,s,s},
{s,s,s,s},
{s,s,s,s},
{s,s,s,s},
};
asm volatile (";start");
for (uint8_t i = 0; i < tab_size(tab); i++){
blabla( pgm_read_word( &tab[i].a ), pgm_read_word( &tab[i].b ),
pgm_read_word( &tab[i].c ), pgm_read_word( &tab[i].d ) );
}
asm volatile (";stop");
T.M.F.
Guest
Fri Jun 12, 2009 4:42 pm
W dniu 12.06.2009 03:54, Zbych pisze:
Quote:
T.M.F. pisze:
No zagladam dosyc czesto przy okazji debuggowania tego co napisalem.
Jakis koszmarnych brakow optymalizacji nie zauwazylem. Mozesz podac
konkretny przyklad w C, ktory jest zle optymalizowany? To zawsze mozna
zglosic jako bug.
To zależy co kto uważa za koszmarne. Poniżej parę przykładów, ale
wątpię, żeby komuś chciało się to czytać.
Wszystkie przykłady były optymalizowane na rozmiar.
---------------------------------------------------------
Testowanie warunków logicznych:
#define PIN1_IS_LOW (!(PINB & (1<<PINB7)))
#define PIN2_IS_LOW (!(PINB & (1<<PINB6)))
volatile uint8_t a;
asm volatile(";start");
a = (PIN1_IS_LOW || PIN2_IS_LOW);
asm volatile(";stop");
Pomijając już tą nieszczęsną promocję do inta, to najbardziej w tym
kodzie rozwaliło mnie wyłuskiwanie 6 bitu.
Promocja do int wynika ze standardu C wiec trudno tu robić zarzut
kompilatorowi. Żeby to ominąć wystarczy explicite typecastowac.
Quote:
Delikatna modyfikacja kodu:
if (PIN1_IS_LOW || PIN2_IS_LOW) a = true;
else a = false;
Tu faktycznie jest jakiś problem z optymalizacja. Warto to zgłosić jako Bug.
Quote:
----------------------------------------------------------
Przygotowanie ramki na stosie dla 5 parametrów 16-bitowych:
1637 0068 ADB7 in r26,__SP_L__
1638 006a BEB7 in r27,__SP_H__
1639 006c 1A97 sbiw r26,10
1640 006e 0FB6 in __tmp_reg__,__SREG__
1641 0070 F894 cli
1642 0072 BEBF out __SP_H__,r27
1643 0074 0FBE out __SREG__,__tmp_reg__
1644 0076 ADBF out __SP_L__,r26
Przy dwóch parametrach:
1751 010e 00D0 rcall .
1752 0110 00D0 rcall .
Łatwo policzyć, że poniżej 8 parametrów bardziej opłaca się wersja z
rcall (program optymalizowany na rozmiar).
Tu sprawa nie jest tak oczywista. Co prawda rcall są krótsze w sensie
długości kodu, ale zajmuja 3 takty zegara. To co generuje gcc zajmuje
niezależnie od długości ramki 9 taktow, czyli 3 rcall. Sytuacja wyglada
jeszcze gorzej kiedy PC jest 22 bitowy wtedy 4 takty. Wiec jak sadze
wybrano pewien kompromis pomiedzy długością kodu a czasem egzekucji.
Quote:
-----------------------------------------------------------
Adresowanie struktur i pobieranie adresów z flasha:
typedef struct{
const prog_char * a;
const prog_char * b;
const prog_char * c;
const prog_char * d;
}Aqq;
prog_char s[] = "Aqq";
Aqq PROGMEM tab[]={
{s,s,s,s},
{s,s,s,s},
{s,s,s,s},
{s,s,s,s},
};
asm volatile (";start");
for (uint8_t i = 0; i < tab_size(tab); i++){
blabla( pgm_read_word( &tab[i].a ), pgm_read_word( &tab[i].b ),
pgm_read_word( &tab[i].c ), pgm_read_word( &tab[i].d ) );
}
asm volatile (";stop");
Pierwszy problem to wyliczanie adresu w flashu:
To zalezy zapewne od zdefiniowanej przez ciebie funkcji tab_size(tab).
Kompilator za każdym obiegiem petli musi ja wywołać, żeby wilczyć wynik
dla danego argumentu dlaczego? Bo ja źle zdefiniowałeś.
Quote:
Pomimo, że jest (czy raczej mogłoby być) użyte adresowanie z
postinkrementacją, to adresy kolejnych elementów są i tak przed każdym
pobraniem na nowo wyliczane. W przykładzie, który podałem daje to 12
niepotrzebnych instrukcji w każdym obiegu tej krótkiej pętli i dodatkowo
zajętą parę rejestrów R19:R18.
Drugi problem, to wyliczanie od początku adresu komórki w tablicy przy
każdym obiegu pętli:
Sprawdziłem to u siebie i nic takiego się nie dzieje. Musiałem
zdefiniować dodatkowo dziwna funkcje Babla bo mi kompilator to
optymalizował i ją wywalał.
Quote:
Wystarczy prosty myk ze wskaźnikiem i pętla się skraca:
asm volatile (";start");
for (uint8_t i = 0; i < tab_size(tab); i++){
const Aqq * p = &tab[i];
blabla( pgm_read_word( &p->a ), pgm_read_word( &p->b ), pgm_read_word(
&p->c ), pgm_read_word( &p->d ) );
}
Tu problem jest bardziej dyskretny. C przy adresowaniu elementów
struktury używa adresowania z przemieszczeniem. W pierwszym przykładzie
pobierasz adresy konkretnego elementu, stad za każdym razem są pobierane
pełne adresy. Za drugim razem używasz adresu struktury i offsetów do jej
elementów (operator -> generuje offsety). Zauważ, że gdyby pola tej
struktury były duże powyżej 127 bajtów, to i tak zaszłaby konieczność
pobrania pełnego adresu, bo offset byłby większy niż maksymalne
przemieszczenie. Oczywiście kompilator może sobie z tym poradzić,
niestety tu wychodzi jedna z wad gcc jest to kompilator pod duże
procki, a AVR jest tylko specyficzną wspieraną platformą. Żeby ta
optymalizacja dobrze zadziałała trzebaby nieźle zamieszać w kodzie gcc.
A w sumie jeśli często się ogląda sesje typu połączony C+assembler to
człowiek szybko się uczy jaka składnia języka jest optymalna. Akurat
przy pętlach ze względu na specyfikę listy rozkazów AVR można się nieźle
wykazać.
Quote:
A podobno gcc ma super optymalizator do pętli (widocznie moja wersja
gdzieś go zgubiła).
Jak ci nie pasuje gcc to zaproponuj cos lepszego... nie mowiac juz o
tym, ze masz kod źródłowy wiec możesz się wykazać dla potomności :)
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
T.M.F.
Guest
Fri Jun 12, 2009 4:47 pm
Quote:
Ooooo to, to

W programach unikam operacji logicznych na bitach
portów, bo wychodzą koszmarki. Czasem zamiast:
if((PINB & (1<<7)) && (PINC & (1<<3))) {...}
lepiej napisać
if(PINB & (1<<7))
if(PINC & (1<<3))
{...}
To się mocno zmieniło w nowszych wersjach gcc. W wielu przypadkach to
się kompiluje do cbi/sbi. Z kolei ja ostatnio używam wyłącznie C++, więc
niewykluczone, że są tu pewne różnice w optymalizacji.
Inna sprawa, że jesli pisze program skladający się z kilku tysięcy linii
to wielu przypadkach efektywność generowanego kodu mnie nie interesuje.
A jesli mam miejsca czasowo krytyczne to zawsze mam inline assembler.
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
T.M.F.
Guest
Fri Jun 12, 2009 5:40 pm
Quote:
Pomijając już tą nieszczęsną promocję do inta, to najbardziej w tym
kodzie rozwaliło mnie wyłuskiwanie 6 bitu.
Promocja do int wynika ze standardu C wiec trudno tu robić zarzut
kompilatorowi. Żeby to ominąć wystarczy explicite typecastowac.
Wyraźnie napisałem, że nie promocja jest tu największym problemem.
Zresztą rzutowanie i tak tego nie poprawia.
U mnie poprawia - najnowszy WinAVR.
Quote:
Łatwo policzyć, że poniżej 8 parametrów bardziej opłaca się wersja z
rcall (program optymalizowany na rozmiar).
Tu sprawa nie jest tak oczywista. Co prawda rcall są krótsze w sensie
długości kodu, ale zajmuja 3 takty zegara.
Specjalnie napisałem, że optymalizacja była ustawiona na rozmiar, więc
rcall powinno mieć pierwszeństwo.
Tak, tylko developerzy sa skupieni na Os - tak to bywa, kiedy zasoby sa
ograniczone.
Quote:
To zalezy zapewne od zdefiniowanej przez ciebie funkcji tab_size(tab).
Kompilator za każdym obiegiem petli musi ja wywołać, żeby wilczyć
wynik dla danego argumentu dlaczego? Bo ja źle zdefiniowałeś.
Zapomniałem wstawić. Oto kod:
#define tab_size(__a) (sizeof(__a)/sizeof(__a[0]))
Jeśli według ciebie jest źle zdefiniowana, to czekam na poprawki.
To powinno w efekcie dac stala. Kiedy zastapilem wywolanie tab_size
stala problem zniknal. Wiec byc moze uzywasz starej wersji?
Quote:
Sprawdziłem to u siebie i nic takiego się nie dzieje. Musiałem
zdefiniować dodatkowo dziwna funkcje Babla bo mi kompilator to
optymalizował i ją wywalał.
Ja na potrzeby przykładu w ogóle jej nie zdefiniowałem (nie jest to
potrzebne, disasemblację zrobiłem po kompilacji, bez linkowania).
To wtedy petla sie totalnie zoptymalizowala i wywalilo mi te wywolania w
ogole.
Quote:
A podobno gcc ma super optymalizator do pętli (widocznie moja wersja
gdzieś go zgubiła).
Jak ci nie pasuje gcc to zaproponuj cos lepszego...
Chciałeś przykłady, to ci je pokazałem. Nie jest w tym przypadku istotne
czy inne kompilatory zrobiłyby to lepiej (a myślę, że IAR byłby lepszy).
IAR zwykle jest lepszy bo jest dostosowany do tej platformy, niestety
uniwersalnosc gcc jest tu jego wada. Z drugiej strony wada ta jest
kompensowana problemami na ktore sie natkniesz probujac przeniesc kod z
IAR na inna platforme.
Quote:
Jak ci nie pasuje gcc to zaproponuj cos lepszego... nie mowiac juz o
tym, ze masz kod źródłowy wiec możesz się wykazać dla potomności :)
Dzięki, już kiedyś próbowałem (na innym porcie gcc). To nie jest robota
na 1 dzień, a ja mam swoją pracę.
Owszem. Ale wystarczy nieco wiedzy jak kompilator generuje kod i
wszystkie pokazane problemy znikaja. Sa co prawda inne, ktorych tak
prosto wyeliminowac sie nie da, ale skoro ich nie pokazales, to pewnie
nie sa dla ciebie tak klopotliwe :)
--
Inteligentny dom -
http://idom.wizzard.one.pl
http://idom.sourceforge.net/
Teraz takze forum dyskusyjne
Zobacz, wyslij uwagi, dolacz do projektu.
J.F.
Guest
Fri Jun 12, 2009 5:46 pm
On Thu, 11 Jun 2009 20:53:28 -0400, T.M.F. wrote:
Quote:
Inaczej kompilator musi utworzyc kopie obiektu tRect i ta kopie dopiero
przekazac do funkcji.
W C++ moze to byc faktycznie kosztowne.
W C tez powinno byc kosztowne. Bo skad kompilator ma wiedziec, ze Rysuj
nie modyfikuje struktury tRect?
Nie musi wiedziec. Ma wrzucic cala na stos, co powinno pojsc dosc
szybko.
Tak sie nie da. Jesli tRect jest gdzies dalej wykorzystywany to
kompilator musi utworzyc jego kopie, zeby zagwarantowac, ze Rysuj jej
nie zmodyfikuje - to wynika ze standardu.
"wrzucenie na stos" to wlasnie utworzenie kopii.
Tylko ze w zwyklym C bez obiektowej nadbudowy jest to operacja dosc
prosta i szybka, jesli struktura krotka. A jak jeszcze procek ma 16
czy 32 bitowe operacje push ...
Quote:
Oczywiscie optymalizator moze
zauwazyc, ze nasze tRect jest dalej niewykorzystywane i z tego etapu
zrezygnowac - no ale to juz zaklada, ze optymalizator jest dosc
sensowny.
Musialby byc IMO az za bardzo sensowny.
Quote:
Ale mowimy konkretnie o AVR i AVR-gcc.
Ja tam patrze troche szerzej.
Jak juz sie pisze w C to dobrze by bylo nie pisac pod jeden procesor,
ktory w dodatku nie ma alternatywnego dostawcy :-)
J.
Goto page Previous 1, 2, 3, 4, 5 Next