Czyli w tym przypadku do chyba by bylo cos takiego:
; divide by 24, no rest
lsr.w #3,D0
move.w D0,D1
lsr.w #2,D0
addx.w D1,D0
lsr.w #2,D0
addx.w D1,D0
lsr.w #2,D0
Krotsze to jest, ale raczej wolniejsze od tablicy, o ile dobrze pamietam cykle dla 68020.
Ta implementacja algorytmu dzielenia przez 24 ma dwa problemy: jest przeznaczona dla 8 bitowego zakresu oraz nie jest do konca poprawna.
Oryginalny target, czyli 6502 nie ma operacji dzielenia, wiec wykorzystano tu pomysl zastapienia jej operacjami, opartymi na przesunieciach I dodawaniu. W przypadku dzielenia przez 24, operacje wartosc/24 mozna przedstawic w formie (wartosc/8)/3, gdzie dzielenie przez 8 mozna zastapic przesunieciem, a jego wynik podzielic przez 3. Dzielenie przez 3 tez trzeba zastapic przesunieciami/dodawaniami, wiec wykorzystano tu jedna z przyblizonych metod dzielenie przez 3 jaka jest wykorzystanie rownania 1/3 = 1/4 +1/16 + 1/64 +..
Problem w tym, ze w pseudokodzie dla 8bit powinno wygladac to tak:
iloraz = (wartosc >> 2) + wartosc
iloraz = (iloraz >> 4) + iloraz
uloraz = iloraz >> 2
natomiast implementacja wyglada tak:
iloraz = (wartosc >> 2) + wartosc;
iloraz = (iloraz >> 2) + wartosc;
iloraz = iloraz >> 2;
Niedokladnosci wynikajace z tej zmiany powoduja, ze juz przy wartosci 33 pojawia sie roznica w wyliczeniach, czyli wartosc dzielona na 24 moze miec gora 263 aby uzyskac dokladny, bezbledny wynik dzielenia. Poprawna wersja algorytmu daje taka dokladnosc w zakresie 0-647.
Druga sprawa to zakres: oryginalny algorytm dziala na bajcie, czyli dzielenie na 3 liczby w zakresie 0-255. Powyzej blad zaczyna sie kumulowac: przy wartosci 1000 jest to juz 5 a przy 8000 41!
Dla 16 bitowych wartosci pseudokod wyglada tak:
iloraz = (wartosc >> 2) + wartosc
iloraz = (iloraz >> 4) + iloraz
iloraz = (iloraz >> 8) + iloraz
iloraz = iloraz >> 2
Wygenerowalem sobie z ciekawosci tabelke porownujaca rozne implementacje:
val val>>3 a_o a_f approx magic abs round val/24
256 (32): 10 10 10 10 10 11 10.666667
264 (33) 10 11 11 11 11 11 11.000000
512 (64): 21 21 21 21 21 21 21.333333
1600 (200) 65 66 66 66 66 67 66.666667
2040 (255) 83 84 85 85 85 85 85.000000
5200 (650) 213 216 216 216 216 217 216.958333
5208 (651): 213 216 217 217 217 217 217.000000
8000 (1000): 328 332 333 333 333 333 333.333333
16000 (2000): 656 664 666 666 666 667 666.666667
32000 (4000): 1312 1328 1333 1333 1333 1333 1333.333333
40000 (5000): 1640 1660 1666 1666 1666 1667 1666.666667
50000 (6250): 2050 2075 2083 2083 2083 2083 2083.333333
60000 (7500): 2461 2490 2500 2500 2500 2500 2500.000000
64000 (8000): 2625 2656 2666 2666 2666 2667 2666.666667
65500 (8187): 2686 2718 2728 2729 2729 2729 2729.166667
W tabelce kolumna a_o oznacza oryginalny kod 8bit w postaci:
wartosc = wartosc >> 3
iloraz = (wartosc >> 2) + wartosc;
iloraz = (iloraz >> 2) + wartosc;
iloraz = iloraz >> 2;
kolumna a_f - kod 8bit poprawiony:
wartosc = wartosc >> 3
iloraz = (wartosc >> 2) + wartosc
iloraz = (iloraz >> 4) + iloraz
uloraz = iloraz >> 2
approx - przyblizone dzielenie z wieksza dokladnoscia, kod dla 16bitowych liczb
wartosc = wartosc >> 3
iloraz = (wartosc >> 2) + wartosc
iloraz = (iloraz >> 4) + iloraz
iloraz = (iloraz >> 8) + iloraz
uloraz = iloraz >> 2
magic - kod dla 16bitych liczb - wyliczanie za pomoca mnozenia przez magiczna wartosc :)
iloraz = ((wartosc >>3 )* 0xAAAB) >> 17
abs, round i wart/24 dla porownania. :)
Przy okazji zaimplementowalem w asm wszystkie algorytmy dla 16bitowych liczb dodatnich aby sprawdzic ile cykli zajmuja.
Co z tego wszystkiego wynika i jaki algorytm dzielenia przez 3 wybrac?
- Jesli piszesz w c, zacznij od sprawdzenia, czy twoj kompilator nie wie lepiej :)- gcc potrafi wygenerowac kod z podobnymi optymalizacjami.
- Najlepiej wybierac takie dzielniki, ktore mozna w calosci zastapic przesunieciami.
- Jelsi jest to niemozliwe, to nalezy rozlozyc operacje tak aby wyodrebnic te, ktore da sie zastapic przesunieciem.
Oczywiscie nie tylko 3 moze byc dzielnikiem, ale jesli juz wybrales 3, to masz nastepujace opcje:
Jak potrzebujesz dokladnosci, korzystasz z mnozenia przez magiczna wartosc:
- gwarantuje dokladnosc dla calego zakresu 16bit.
- stosunkowo wolne
mulu.w #$aaab, d0 ; 70 (38+2n)
swap d0 ; 4
lsr.w #1, d0 ; 8
Dla 68000 w najgorszym przypadku to 82 cykle, dla zakresu wartosci 0-1023 to w najgorszym przypadku 70 cykli, czyli bedzie szybszy od algorytmu 16 bitowego ponizej!
Dla 68030 bedzie to okolo 36 cykli.
Jak dokladnosc nie jest kluczowa a zakres wartosci 16bit, korzystasz z algorytmu z przyblizonym dzieleniem przez 3 dla wartosci 16 bit:
- szybszy od mnozenia
- dokladny w calym zakresie, rzadko moze sie pojawic blad, gora +-1
move.w d0,d1 ; 4
lsr.w #2, d0 ; 10
addx.w d1,d0 ; 4
move.w d0,d1 ; 4
lsr.w #4, d0 ; 14
addx.w d1,d0 ; 4
move.w d0,d1 ; 4
lsr.w #8, d0 ; 22
addx.w d1,d0 ; 4
lsr.w #2,d0 ; 10
Czas wykonania dla 68000 to 80 cykli.
Dla 68030 bedzie to okolo 28 cykli.
Jak dokladnosc nie jest kluczowa a zakres wartosci <81, korzystasz z algorytmu z przyblizonym dzieleniem przez 3 dla wartosci 8 bit:
- duzo szybszy od mnozenia
- dokladny do wartosci <81, powyzej z bledem, rosnacym wraz z wartoscia.
move.w d0,d1 ; 4
lsr.w #2, d0 ; 10
addx.w d1,d0 ; 4
move.w d0,d1 ; 4
lsr.w #4, d0 ; 14
addx.w d1,d0 ; 4
lsr.w #2,d0 ; 10
Czas wykonania dla 68000 to 50 cykli.
Dla 68030 bedzie to okolo 20 cykli.
ps: Chyba za duzo wolnego czasu ostatnio mam :)