Ahoj. Jen pár nepodstatných komentářů.
Ve tvém psaní se často uvádí něco jako "zvýšit o 4 bity", "posunutí o 4 bity" apod. Mám v tom zmatek. Jedná se o překlep a mají tam být "4 bajty"? Ne, že by "posunutí o 4 bity" nešlo, ale význam je zcela odlišný.
Ohledně rozhodování, jaké instrukce použít při řešení problému: Na základní 68k CPU jsou pravidla jednoduchá, triviální: Použít takové instrukce, jejichž počet taktů k provedení je v součtu vždy minimální
U CPU 020 se to komplikuje kvůli instrukční cache a u 030+ ještě víc kvůli datové cache.
Když si nejsem s časováním jistý, tak vždy nakouknu do tabulky a seznamu
http://oldwww.nvg.ntnu.no/amiga/MC680x0 ... iming.HTMLZ tabulky pak dobře vyplývají určité zvláštnosti a techniky, které rozumný programátor na 68k používá.
Například je všeobecně známé, že když to jde, používáme MOVEQ místo MOVE nebo CLR. MOVEQ ale umí nastavit jen signed 8bit hodnotu a vždy ji rozšíří na celý rozsah 32 bitů. Takže ne vždy jde použít. Pokud se přičítá nebo odečítá jen malé číslo v rozsahu čtyř bitů, použije se ADDQ nebo SUBQ. U datových registrů se dá volit, jestli je operace jen nad 16 nebo celými 32 bity registru (addq.w/addq.l).
Je také dobré si uvědomit, že jakékoliv aritmetické operace s adresovými registry pracují vždy s plným rozsahem 32 bitů. Z toho vyplývá několik vlastností: addq.w #x,Dx je rychlejší než addq.w #x,Ax, protože to druhé se vždy rozšiřuje na 32 bitů. Ze stejného důvodu je add.w #x,Ax pomalejší než add.w #x,Dx, a (Ax,Dx.w) je pomalejší než (Ax,Dx.l). Na druhou stranu nám to umožňuje snadno přidávat 16bit offsety (add.w Dx,Ax místo add.l Dx,Ax). Proto to tak Motorola udělala.
Pokud se adresuje paměť, vždy je lepší používat adresový mód než aritmetiku rozepisovat (fetch=načtení instrukce vždy trvá 2 takty). Takže raději add.w offs(A0,d0.w),d1 než add.w d0,a0 + add.w offs(a0),d1. Samozřejmě to platí jen pokud je to možné (v prvním případě může být "offs" jen osmibitový).
Je dobré si uvědomit, že postinkrementace (Ax)+ je "zadarmo". Tedy že není rozdílu mezi (Ax) a (Ax)+. Je tedy výrazně rychlejší než nějaké move.w (Ax),Dx + addq.w #2,Ax.
Ale predekrementace -(Ax) už zadarmo není a vyžaduje dva takty k instrukci navíc!
Pokud je to možné, tak si uvědomíme, že adresace s offsetem d(Ax) je vždy o 4 takty pomalejší (2 takty na fetch wordu + 2 takty na aritmetiku) než (Ax) nebo (Ax)+. Takže když to jde, tak čteme nebo zapisujeme data postupně a ne na přeskáčku.
Jedny z nejpomalejších instrukcí jsou DIVS/DIVU, MULS/MULU a rotace a posuny. Hlavně na ty druhé se zapomíná.
Všechny rotace a posuny trvají 6(nebo 8)+2*n taktů, kde n je počet posunů. Proto nenápadně vypadající instrukce asr.l #8,d0 (tak často používaná při fixed-point aritmetice) trvá mnohdy smrtících 24 taktů
Často se proto volí spíše jednodušší swap pro získání celé části hodnoty (fixed-point formát je pak samozřejmě 16:16). Pokud to tedy je možné a nebo je třeba použít 32 bitů.
CLR instrukce je spíše zbytečná. Na mazání datového registru je rychlejší moveq, na smazání adresového se dá klidně použít sub Ax,Ax (i když je to časově stejné) a na mazání paměti je lepší používat movem, když je to možné.
MULU/MULS jsou v časování zajímavé tím, že záleží na hodnotě prvního operandu (viz odkaz). Paradoxně tak může být MULS rychlější než MULU. Naopak rychlost MULU je snadnější předvídat (záleží jen na počtu bitů nastavených na 1). Obecně ale stejně platí, že když je to možné, tak je dobré použít bitové posuny + aritmetiku nebo tabulku.
ADD.W Dx,Dx je rychlejší než LSL.W #1,Dx. ADD.W + ADD.W je pořád rychleší než LSL.W #2!
DIVU/DIVS je bohužel zabiják výkonu. 140 (resp. 158) taktů (plus mínus 10%)! Takže pokud je to jen možné, je třeba použít bitové posuny pro dělení mocninou dvěma. Nebo využít tabulky. Nebo zvážit úpravu celého algoritmu, aby dělení nebylo zapotřebí.
Skoky (Bcc instrukce) jsou pomalé. Když už je potřebujeme, tak volíme krátký skok BCc.b (někteří píšou .s místo .b). Instrukce DBCc trvá 10 až 14 taktů (obvykle 12 - DBF instrukce). Proto je lepší krátké smyčky rozvinout, když se neopakují příliš často (k tomu je dobré používat REPR makra, například). BSR/JSR + RTS je pomalé a pokud je to možné dá se použít rychlejší lea.l .return(pc),Ax + bra.b ... + jmp (Ax). Samozřejmě pokud nám jde o každý takt, protože čitelnost programu dostane pořádnou ránu
V případě nedostatku registrů je vhodné buď pracovat jen s 16 bity a využít celý rozsah datových registrů a prohazovat poloviny pomocí SWAP. Nebo využít adresové registry. A taky je možno použít A7, pokud víme co děláme (pozor na přerušení pokud kód už běží v privilege módu!). V tom případě je asi vhodné rozlišovat používaný registr jako A7 od operací nad standardním zásobníkem (SP), abychom se v tom nepoztráceli.
Když už není kde brát a potřebujeme si něco do paměti uložit, tak zvážíme jestli k tomu nevyužít zásobník ( -offset(SP) ), nebo adresaci pomocí PC (např. add.w .value(pc),Dx). Bohužel PC-relative adresace nemůže být používána jako cílový operand
Vše je lepší než používat celou 32 bitovou absolutní adresu. Zvážíme taky výhody relativní PC adresace s ofsetem a registrem: d(PC,Dx). Bohužel offset "d" může být jen osmibitový, takže data musíme ukládat blízko našemu kódu (a když tak data přeskočit BRA instrukcí - pořád to může být výhodné).
TST instrukci použijeme jen v krajní nouzi. Je dobré nezapomínat, že stavové bity jsou nastavovány většinou instrukcí pokud se pracuje s datovými registry nebo pamětí (nenastavují se při aritmetice s adresovými registry!). Takže raději move.w (Ax),Dx + beq.b, když stejně hodnotu v Dx použijeme, namísto TST.w (Ax) + beq.b + ...
Ošetření přerušení je relativně pomalá záležitost. Procesor musí uložit návratovou adresu, udělat odskok, přerušovací rutina často musí uložit obsah registrů (movem), pak je na konci zase načíst (další movem) a následuje RTE, které samo trvá 20 taktů... Takže když nemusíme, neděláme to
U procesorů 020+ pak platí jiné závislosti. Naprosto zásadní je správné používání instrukční a datové (030+) cache. Rozvinování smyček je proto třeba dělat jen do určité velikosti (aby nevznikaly cache-miss). Data je vhodné mít "packed" pěkně pohromadě (takže více polí hodnot než jedno pole velkých struktur). Některé věci jsou výrazně rychlejší (posuny a rotace mají konstantní čas, násobení a dělení je rychlejší, ...).
http://oldwww.nvg.ntnu.no/amiga/MC680x0 ... index.HTMLU 040 a 060 je zásadní počítat s instrukční pipeline, kterou už CPU má -- tedy paralelní fetch, decode a execution instrukce (060 navíc spekulativní out-of-order execution). Takže je vhodné instrukce řadit tak, aby zdrojový operand aktuální instrukce nezávisel na cílovém operandu předcházející instrukce a CPU tak nemuselo čekat. Instrukce mohou běžet paralelně a pokud chceme ručně optimalizovat, musíme s tím počítat a podle toho se zařídit. ( Poznámka: U složitějších CPU je pak už obtížné ručně optimalizovat a je lepší to nechat na překladači (bolestivě zjištěno už v roce 2000 na tehdejším Celeronu a Athlonu
). )
Těch různých technik a dobrých "rad" by bylo určitě víc. A hodně by jich souviselo s Amigou a specifikami jejího hardware.
Ale stará definice říká, že program je algoritmus pracující nad daty.
Největší optimalizace se tedy dosáhne tím, že se tyto dvě složky chytře navrhnou.