
LEZIONE13:  OTTIMIZZAZIONE DEL CODICE ASSEMBLY

Autori: Fabio Ciucci, Ugo Erra

Ringraziamenti: Michael Glew, 2-Cool/LSD, Subhuman/Epsilon


Scrivere routine in assembly non significa necessariamente che il proprio
codice girer al massimo della velocit. Infatti non sempre il codice
assembly pu essere classificato come il meglio ottenibile in termini di
velocit. Consideriamo infatti i numerosi demo che esistono in circolazione
ed esattamente quelli che trattano grafica 3d, nella maggior parte delle
volte (quasi sempre) le routine che stanno sotto ad effetti quali rotazioni,
zoom, esplorazioni di mondi, etc, sono le stesse, ma la loro implementazione
in codice assembly  differente, poich ogni programmatore cerca di 
implementarle nel modo migliore possibile cio in modo che girino al massimo
della velocit. Ci  realizzato con tecniche di ottimizzazione che ogni
buon coder assembly deve sapere. Le tecniche sono numerose e sicuramente
ci vuole un bel p di tempo prima che si inizino ad utilizzare in modo
del tutto naturale. Esistono vari tipi di ottimizzazione e molte di queste
tecniche che andr a spiegare sono valide per il 68000 ma le stesse sono
anche inutili in microprocessori quali il 68040 o il 68060.
La prima cosa che bisogna avere disponibile  una tavola dei cicli macchina
di ogni istruzione del 68000, che troverete sintetizzata in questa lezione:
dando un rapido sguardo a questa tavola vi potreste meravigliare osservando
il "tempo" che impiega ogni istruzione ad essere eseguita, e forse fino a
questo punto credevate che ogni istruzione fosse eseguita nello stesso tempo;
ebbene vi sbagliavate!!!
Infatti, come primo aproccio, notate il tempo che impiega un'istruzione di
moltiplicazione (MULU) rispetto ad una di addizione(ADD), e capirete
immediatamente per quale motivo l'ottimizzazione  importante:

	ADD	; tempo di esecuzione: dai 6 ai 12+ cicli di clock

	MULS	; tempo di esecuzione: 70+ cicli di clock

Quindi, si capisce facilmente come ottimizzare questa istruzione:

lento:		MULU.W	#2,D0	; 70+ cicli

ottimizzato:	ADD.W	d0,d0	; 6+ cicli

Vi anticipo che le moltiplicazioni e le divisioni sono le due istruzioni
pi lente. Vediamo un approssimativo elenco delle istruzioni ordinate
dalle pi veloci alle pi lente: (i cicli sono nella migliore ipotesi!)

EXT, SWAP, NOP, MOVEQ	; 4 cicli -> le pi veloci!

TST, BTST, ADDQ, SUBQ, AND, OR, EOR	; 4 + indirizzamento, velocine...

MOVE, ADD, SUB, CMP, LEA	; 4+ indirizzamento, ma spesso gli
				; indirizzamenti sono "pesanti" da eseguire

Poi abbiamo BCLR/BCHG/BSET con 8+, LSR/LSL/ASR/ASL/ROR/ROL con 6 +2n, dove
n  il numero di shifts da fare, infine abbiamo:

	MULS/MULU	; 70+ !
	DIVU		; 140+ !!
	DIVS		; 158+ !!!

Occorre anche ricordare che:

	BEQ,BNE,BRA...	; 10
	DBRA		; 10
	BSR		; 18
	JMP		; 12
	RTS		; 16
	JSR		; 16/20

Quindi, attenzione a non fare troppe chiamate alle subroutine, perch ogni
BSR+RTS per tornare vi mangia 18+16=34 cicli almeno!
Mettete sempre nel loop principale le subroutines corte,  uno spreco
perdere 34 cicli di BSR+RTS per eseguire una manciata di istruzioni!

EXAMPLE:
	BSR.S	ROUT1
	BSR.S	ROUT2
	BSR.S	ROUT3
	RTS

ROUT1:
	MOVE.W	d0,d1
	RTS
ROUT2:
	MOVEQ	#0,d2
	MOVEQ	#0,d3
	RTS
ROUT3:
	LEA	label1(PC),A0
	RTS

Versione che salva 34*3= 96 cicli:

EXAMPLEFIX:
	MOVE.W	d0,d1
	MOVEQ	#0,d2
	MOVEQ	#0,d3
	LEA	label1(PC),A0
	RTS

Oltre all'instruzione in s, conta anche il modo di indirizzamento usato.
Per esempio:

	MOVE.L	(a0),d0

 pi lento di:

	MOVE.L	$12(a0,d1.w),LABEL1

Eppure si tratta sempre di istruzioni MOVE. Comunque pu apparirvi molto
logico il perch della maggior lentezza della seconda istruzione rispetto
alla prima: il processore deve calcolare l'offset sommando ad a0 il valore
di d1 pi il $12, poi fare la copia, e dove? In memoria, ad una label,
anzich in un registro, cosa ben pi lenta dato che i registri sono DENTRO
il processore, mentre la memoria  fuori, e per raggiungerla il dato deve
passare dai fili della scheda madre!!!!!

*****************************************************************************
* OTTIMIZZAZIONI DI PRIMO LIVELLO: LO "SCAMBIO" E LA "SCELTA" DI ISTRUZIONI *
*****************************************************************************

Ecco i modi di indirizzamento ordinati dal pi veloce al pi lento:
NOTA: i numeri dopo il ; sono i cicli di clock da aggiungere al tempo usato
dall'istruzione, nei casi di byte-word/longword


Registro dati diretto					  Dn/An	    ; 0

Registro indirizzi indiretto (o con Post-Incremento)	 (An)/(An)+ ; 4/8
Immediato						  #x	    ; 4/8

Registro indirizzi indiretto con Pre-Decremento		 -(An)	    ; 6/10

Registro indirizzi indiretto con Offset (max 32767)	 w(An)	    ; 8/12
Assoluto corto						   w	    ; 8/12
Program Counter con Offset (calcolato dall'asmone)	 w(PC)	    ; 8/12

Program Counter con Offset e Indice			b(PC,Rx)    ; 10/14
Registro indirizzi indiretto con Offset e Indice	b(An,Rx)    ; 10/14

Assoluto lungo						   l	    ; 12/16


Come si pu notare, mentre un "MOVE.L LABEL1,LABEL2" solo di indirizzamento
porta via 16+16 = 32 cicli, un "MOVE.L #1234,d0" porta via solo 8+0 = 8 cicli.
Appare evidente come le istruzioni .W siano pi veloci di quelle .L, per
esempio l'indirizzamento (An), se .W impiega 4 cicli, se .L 8 cicli!

Comunque questi esempi sono MOLTO indicativi, infatti anche studiando con
le tabelle alla mano  difficile calcolare veramente il tempo di
esecuzione della routine. Ma saremo sempre sicuri che il BSR  pi veloce
del JSR, che l'ADDQ  pi veloce dell'ADD, e soprattutto che ogni volta
che si riesce a sostituire un MULU/DIVU/MULS/DIVS con qualcos'altro abbiamo
certamente velocizzato il tutto!

Qua stiamo parlando di "cambi di istruzione", ossia di piccole modifiche
fatte sostituendo istruzioni lente con altre pi veloci. Per l'arte delle
ottimizzazioni, vera regina della scena demo, comporta anche l'utilizzazione
di una tabella "precalcolata" invece di implementare una mega funzione che
d gli stessi risultati, e altre infinite cose.

Per c' anche il rovescio della medaglia: il codice megaottimizzato con
tabelle e altri stratagemmi spesso diviene meno leggibile e capibile, e
meno "modificabile". Quindi, state attenti ad evitare l'errore nel quale
molti di noi sono caduti, ossia nel voler ottimizzare la routine prima
di averla finita, passo passo, ad ogni costo. Questo non fa che rallentare
lo sviluppo della routine in questione, specialmente se si  alle prime
armi, infatti a che serve una routine megaottimizzata che calcola la
prospettiva, se non riusciamo pi a scriverci "intorno" la routine di
disegno e rotazione del solido? O addirittura non capiamo pi come mai
sta funzionando? ---->>>>> MAI PASSARE SOTTO OTTIMIZZAZIONE UNA ROUTINE CHE
NON  COMPLETAMENTE TERMINATA E FUNZIONANTE; INOLTRE, UNA VOLTA CHE  PRONTA
PER L'OTTIMIZZAZIONE, RICORDARSI DI TENERE LE COPIE DEI LISTATI DEI VARI
PASSAGGI DELL'OTTIMIZZAZIONE, IN QUANTO SPESSO OCCORRE "TORNARE INDIETRO"
E MODIFICARE QUALCOSA!!! POI RIOTTIMIZZEREMO LA VERSIONE MODIFICATA!

Questo avvertimento vi suoner strano, perch sembra che un listato, una
volta ottimizzato, diventi irriconoscibile e incomprensibile anche per
l'autore. Ebbene, se  MOLTO ottimizzato, questo pu succedere!
Comunque ricordatevi che le ottimizzazioni vanno effettuate in parti del
listato che effettivamente richiedono molto tempo per essere eseguite: ad
esempio,  inutile stare ad ottimizzare una routine che viene eseguita una
sola volta alla startup, o una sola volta per fotogramma. Le prime routines
da ottimizzare sono quelle che vengono eseguite molte volte per fotogramma,
ossia quelle nei loop dbra, o comunque in cicli vari. Ad esempio, vediamo
questo listatino:

Bau:
	cmp.w	#$ff,$dff006	; Aspetta il Wblank
	bne.s	Bau
	bsr.s	routine1
	bsr.s	routine2
	btst	#6,$bfe001	; Aspetta il mouse
	bne.s	Bau
	rts

Routine1:
	move.w	#label2,d6
	move.w	d0,d1
	move.w	d2,d3
	and.w	d4,d5
	rts

Routine2:
	move.w	#200,d7
	lea	label2(PC),a0
	lea	label3(PC),a1
loop1:
	move.w	(a0)+,d0
	move.w	(a0)+,d1
	add.w	d0,d5
	add.w	d0,d6
	move.w	d5,(a1)+
	move.w	d5,(a2)+
	dbra	d7,loop1
	rts

In questo caso, appare evidente che il 99% del tempo lo si perde eseguendo
200 volte il loop della routine2. Di conseguenza, se si ottimizasse questo
loop rendendolo veloce il doppio, l'intero programma girerebbe al doppio
della velocit, mentre se si facesse andare anche al triplo o al quadruplo
della velocit la routine2, non si noterebbe nemmeno la differenza!!!!!
Per vedere quante "linee raster" occupa una routine, basta usare il vecchio
sistema di cambiare colore all'inizio della routine, e cambiarlo nuovamente
alla sua fine. In questo modo la "striscia" del colore cambiato indicher
il tempo in "linee video" usato per l'esecuzione:

Bau:
	cmp.w	#$90,$dff006	; Aspetta il Wblank
	bne.s	Bau
	bsr.s	routine1
	move.w	#$F00,$dff180	; Color0: ROSSO
	bsr.s	routine2
	move.w	#$000,$dff180	; Color0: NERO
	btst	#6,$bfe001	; Aspetta il mouse
	bne.s	Bau
	rts

In questo caso, aspettiamo la linea $90, verso la met dello schermo,
facciamo eseguire la routine1, poco importante, poi cambiamo colore (rosso),
facciamo eseguire la routine2, e ricambiamo colore (nero).
Apparir a video una strisciata rossa... quello  il "tempo" in cui viene
eseguita routine2. Per vedere se si migliora o si peggiora la velocit,
baster vedere se la striciata si allunga o si accorcia.
Alcuni maniaci (come il mio amico hedgehog), appiccicano un pezzo di
nastro adesivo sul monitor all'altezza dell'ultima linea colorata, in modo
da notare ad ogni modifica ogni lieve miglioramento o peggioramento.
Io, personalmente, ci metto un dito o vado ad occhio... fate voi!
Abbiamo comunque gi visto questo sistema nella lezione sul blitter e
in Lezione11n1.s e seguenti, per "visualizzare" il tempo aspettato tramite
i chip CIAA/CIAB. A proposito, potreste usare anche i timer per calcolare i
tempi "numericamente", ma il sistema del cambio di colore  pi immediato.

Ma prima cominciamo con le ottimizzazioni elementari, che occorrerebbe saper
fare "in diretta" mentre si scrive. La cosa pi semplice  sapere quale
istruzione scegliere tra quelle possibili, quando si vuol fare un dato
compito. Infatti la stessa operazione si pu fare in pi modi!
Ad esempio, vediamo questo listato:

	lea	LABEL1,a0
	move.l	0(a0),d0
	move.l	2(a0),d1
	ADD.W	#5,d0
	SUB.W	#5,d1
	MULU.W	#2,d0
	MOVE.L	#30,d2
	RTS

La stessa cosa, si pu fare scegliendo queste istruzioni:

	lea	LABEL1(PC),a0	; Indirizzamento (PC) pi veloce
	move.l	(a0),d0		; Non occorreva l'offset 0!!
	move.l	2(a0),d1	; Questa si lascia cos
	ADDQ.W	#5,d0		; numero minore di 8, si pu usare ADDQ!
	SUBQ.W	#5,d1		; idem, per SUBQ!
	ADD.W	d0,d0		; salvo 60 cicli!! D0*2  uguale a D0+D0!!!
	MOVEQ	#30,d2		; numero minore di 127, posso usare MOVEQ!
	RTS

La routine  mooolto pi veloce, ed  sempre leggibilissima. Quindi, la
prima cosa da imparare  stare attenti ad usare le istruzioni Quick dedicate
come ADDQ/SUBQ/MOVEQ nel caso il numero sia piccolo abbastanza, a togliere
le moltiplicazioni e le divisioni quando possibile, ad usare indirizzamenti
relativi al (PC) o a registri+offset, anzich a LABEL nude e crude,
eccetera. Con un p di esperienza, vi verr naturale scegliere le istruzioni
pi veloci, e scriverete al primo colpo listati come il secondo, anzich
listati come il primo presentato, che spero gi ora non scriviate!!!!
Ecco un'altro esempio di ottimizzazione "a scambio" di istruzioni:

	Move.l	#3,d0		; 12 cicli
	Clr.l	d0		; 6 cicli
	Add.l	#3,a0		; 16 cicli
;
	Move.l	#5,Label	; 28 cicli

Versione ottimizzata "a scambio":

	Moveq	#3,d0		; 4 cicli
	Moveq	#0,d0		; 4 cicli
	Addq.w	#3,a0		; 4 cicli
;
	Moveq	#5,d0		; 4 cicli
	Move.l	d0,Label	; 20 cicli, totale 24 cicli

Potrei continuare ancora per molto con esempietti del genere, ma non dovete
conoscere a memoria tutti i casi possibili, naturalmente!
Occorre piuttosto capire "il metodo", la filosofia del coding ottimizzato.
Esistono, ad esempio, tecniche per velocizzare il caricamento di valori a
32 bit nei registri:

	move.l	#$100000,d0	; 12 cicli

Versione ottimizzata:

	moveq	#10,d0		; 4 cicli
	Swap	d0		; 4 cicli, totale 8 cicli

Altra cosa IMPORTANTISSIMA  che l'accesso alla memoria (ossia alle label)
 molto pi LENTO dell'accesso ai registri dati ed indirizzi. Quindi, 
una buona abitudine il tendere ad usare tutti i registri e badare di toccare
il meno possibile le label. Ad esempio il listato:

	MOVE.L	#200,LABEL1
	MOVE.L	#10,LABEL2
	ADD.L	LABEL1,LABEL2

Si pu ottimizzare MOLTISSIMO scrivendo:

	move.l	#200,d0
	moveq	#10,d1
	add.l	d0,d1

Non fate caso alla stupidit dell'esempio, ma al fatto che mentre nel primo
abbiamo fatto 4 accessi alla lentissima RAM, facendo passare i dati per i
fili aggrovigliati della scheda madre, nel secondo caso tutto si  svolto
all'interno della CPU, turbizzando il tutto. Se finite i registri dati, usate
anche i registri indirizzi per tenere dati, piuttosto che accedere a label!
Inoltre, se  possibile, usate istruzioni .W anzich .L, per esempio il
listato di prima si potrebbe riottimizzare in:

	move.w	#200,d1
	moveq	#10,d0
	add.w	d0,d1

In questo caso le istruzioni occupano 8 cicli anzich 12... e non  poco!
State per attenti che la word alta sia azzerata e/o non serva mai!!

Comunque, le ottimizzazioni "a scambio" pi profique sono quelle che
eliminano istruzioni di moltiplicazione (70 cicli) e di divisione (158 cicli),
e si pu dire che  nata una scienza in proposito.
Il caso pi semplice  quando dobbiamo dividere o moltiplicare per numeri
che sono potenze di 2, perch possiamo adoperare le istruzioni di shift
che impiegano come cicli macchina esattamente:

	Lsl.w	6+2n		; n = numero di shifts
	Asr.w	6+2n
	Lsr.l	8+2n
	Asr.l	8+2n

Qui n sta ad indicare il numero di bit, ed il numero dei cicli  riferito
a quando si adoperano i registri.
La regola da seguire in genere  la seguente: (per MULS o MULU)

Nota: alle volte ci vuole un EXT.L D0 prima degli ASL che sostituiscono i MULS,
mentre prima di quelli che sostituiscono i MULU pu servire una pulizia
della word alta con "swap d0, clr.w d0, swap d0".

MULS.w	#2,d0		| ADD.L d0,d0 ; mi pare chiaro!

MULS.w	#4,d0		| ADD.L d0,d0 ; anche questo!
			| ADD.L d0,d0

MULS.w	#8,d0		| ASL.l #3,d0 ; da 8 a 256 conviene l'asl
MULS.w	#16,d0		| ASL.l #4,d0
MULS.w	#32,d0		| ASL.l #5,d0
MULS.w	#64,d0		| ASL.l #6,d0
MULS.w	#128,d0		| ASL.l #7,d0
MULS.w	#256,d0		| ASL.l #8,d0

Se ci sono problemi coi MULU, si potrebbe pulire la word alta:

mulu.w #n,dx ->	swap dx		;n is 2^m, 2..2^8
		clr.w dx	;(2,4,8,16,32,64,128,256)
		swap dx
		asl.l #m,dx

Per il muls pu bastare mettere un "ext.l" prima dell'asl.

muls #n,dx ->	ext.l dx	;n is 2^m, 2..2^8
		asl.l #m,dx

Mentre per le DIVISIONI:

DIVS.w	#2,d0		| ASR.L #1,d0	; attenzione: IGNORA IL RESTO!!!!!!!
DIVS.w	#4,d0		| ASR.L #2,d0
DIVS.w	#8,d0		| ASR.L #3,d0
DIVS.w	#16,d0		| ASR.L #4,d0
DIVS.w	#32,d0		| ASR.L #5,d0
DIVS.w	#64,d0		| ASR.L #6,d0
DIVS.w	#128,d0		| ASR.L #7,d0
DIVS.w	#256,d0		| ASR.L #8,d0
DIVU.w	#2,d0		| LSR.L #1,d0	; attenzione: IGNORA IL RESTO!!!!!!!!
DIVU.w	#4,d0		| LSR.L #2,d0
DIVU.w	#8,d0		| LSR.L #3,d0
DIVU.w	#16,d0		| LSR.L #4,d0
DIVU.w	#32,d0		| LSR.L #5,d0
DIVU.w	#64,d0		| LSR.L #6,d0
DIVU.w	#128,d0		| LSR.L #7,d0
DIVU.w	#256,d0		| LSR.L #8,d0

Come sapete, dopo una divisione nella word bassa rimane il risultato e in
quella alta il resto; se sostituite il DIVS/DIVU con uno shift invece avrete
il risultato nella word bassa e la word alta azzerata... quindi NON  LA
STESSA COSA, state attenti!
Nel caso peggiore in cui n=8 otterete un numero di cicli esattamente di
6+2*8=22 cicli per le word e 8+2*8=24 cicli per le longword, quindi il
risparmio  garantito. Inoltre sappiate che su un 68020 il numero dei cicli
per le istruzioni di shift  lo stesso indipendentemente dal numero di
bit da spostare. Inoltre tenete anche presente l'istruzione Swap, che
impiega 4 cicli ad essere eseguita, poich ci pu far comodo in molte
situazioni in cui il numero di bit da spostare  consistente. Vediamo
a questo proposito una serie di esempi:

; Shift di 9 bit a sinistra

	Lsl.l	#8,d0
	Add.l	d0,d0

; Shift di 16 bit a sinistra

	Swap	d0
	Clr.w	d0

; Shift di 24 bit a sinistra

	Swap	d0
	Clr.w	d0
	Lsl.l	#8,d0

; Shift di 16 bit a destra

	Clr.w	d0
	Swap	d0

; Shift di 24 bit a destra

	Clr.w	d0
	Swap	d0
	Lsr.l	#8,d0

Come potete vedere le tecniche per shiftare non mancano e se ne possono
ricavare moltissime, come sempre spetta a voi entrare nell' ottica giusta
e cercare di fare l'ottimizzazione che cercate. Quindi per potenze di 2
non avete grossi problemi a moltiplicare e a dividere in un tempo decente.
I problemi potrebbero sorgere nel caso in cui il numero non sia 
una potenza di due; in effetti questo  vero, ma per molti valori possiamo
ancora aggirare il problema. Infatti consideriamo il caso in cui dobbiamo
moltiplicare il valore, contenuto in un registro, per 3: ebbene, pensate
al fatto che dovete eseguire una espressione del genere 3*x, che potete anche
scrivere 2*x+x. A questo punto avete risolto il vostro problema perch il
vostro codice sar:

	Move.l	d0,d1
	Add.l	 d0,d0 ; d0=d0*2
	Add.l	 d1,d0 ; d0=(d0*2)+d0

Consideriamo un altro caso ad esempio per n=5, allora abbiamo 5*x, ossia
4*x+x: come codice avremo che:

	Move.l	d0,d1
	Asl.l	#2,d0 ; d0=d0*4
	Add.l	d1,d0 ; d0=(d0*4)+d0

Consideriamo infine un altro caso in cui n=20, allora abbiamo 20*x, ma
20*x = 4*(5*x) = 4*(4*x+x)

	Move.l	d0,d1
	Asl.l	#2,d0 ;d0=d0*4
	Add.l	d1,d0 ;d0=(d0*4)+d0
	Asl.l	#2,d0 ;d0=4*((d0*4)+d0)

In breve possiamo tentare di fare una cosa del genere, se scomponendo il
numero in fattori primi notiamo che ci sono molti 2; ma fatevi sempre
un conticino sul numero dei cicli per vedere se vi conviene oppure no.
Molti di voi potrebbero meravigliarsi nel vedere qui trattato il modo per
poter ottimizzare una semplice Mulu o Divu, ma pensate i casi in cui queste
si trovino in cicli, in questo caso queste tecniche sono realmente molto
utili, comunque anche se la Mulu non si trova in un ciclo, che vi costa
sostituirla con qualcosa di migliore ?
Poich siamo in argomento parliamo molto brevemente delle implementazioni 
delle espressioni in Assembly.
Quello che vi dir non  niente di particolare ma spesso non si presta
attenzione ad un fatto banale.
Quando dobbiamo implementare una funzione, di solito quello che facciamo 
di caricare i valori nei registri ed effettuare tutte le operazioni.
In generale, per risparmiare tempo macchina nella valutazione della funzione,
conviene utilizzare i metodi di raccoglimento che si imparano alle superiori,
infatti consideriamo una espressione banale:

a*d0+b+d1+a*d3+b*d5 puo essere scritta come:

a*(d0+d3)+b*(d1+d5)

In questo modo risparmiamo due moltiplicazioni.

Dato che per saper scegliere la giusta istruzione basta conoscere in ogni
coppia di istruzioni equivalenti quale  la pi veloce, vi presento una
tabella simile a quella alla fine di 68000-2.txt, con istruzioni "lente", ed
equivalenti "veloci" da usare:

 ISTRUZIONE esempio	| EQUIVALENTE, MA PI VELOCE
------------------------|-----------------------------------------------
add.X #6,XXX		| addq.X #6,XXX		(massimo 8)
sub.X #7,XXX		| subq.X #7,XXX		(massimo 8)
MOVE.X LABEL,XX		| MOVE.X LABEL(PC),XX	(se nella stessa SECTION)
LEA LABEL,AX		| LEA LABEL(PC),AX	(se nella stessa SECTION)
MOVE.L #30,d1		| moveq #30,d1		(min #-128, max #+127)
CLR.L d4		| MOVEQ #0,d4		(solo per i registri dati)
ADD.X/SUB.X #12000,a3	| LEA (+/-)12000(a3),A3	(min -32768, max 32767)
MOVE.X #0,XXX		| CLR.X XXX		; muovere #0  stupido!
CMP.X  #0,XXX		| TST.X XXX		; il TST dove lo lasci?
Per azzerare un reg. Ax	| SUB.L A0,A0		; meglio di "LEA 0,a0".
JMP/JSR	XXX		| BRA/BSR XXX		(Se XXX  vicino)
MOVE.X #label,AX	| LEA label,AX		(solo registri indirizzi!)
MOVE.L 0(a0),d0		| MOVE.L (a0),d0	(togli l'offset se  0!!!)
LEA	(A0),A0		| HAHAHAHA!             ; Toglila, non ha effetti!!
LEA	4(A0),A0	| ADDQ.W #4,A0		; fino ad 8
addq.l #3,a0		| addq.w #3,a0		; Solo reg. indirizzi, max 8
Bcc.W label		| Bcc.S label           ; Beq,Bne,Bsr... dist. <128

Per le moltiplicazioni e divisioni di multipli di 2 convertite in ASL/ASR
vedete la tabella sopra. 
Seguono dei casi particolari per cambiare MULS/MULU in qualcos'altro:

NOTA: Se si tratta di un "MULS", spesso occorre aggiungere un "ext.l dx"
come prima istruzione, per estendere il segno a longword.

mul*.w #3,dx -> move.l dx,ds
		add.l dx,dx
		add.l ds,dx
------------------------------------
mul*.w #5,dx -> move.l dx,ds
		asl.l #2,dx
		add.l ds,dx
------------------------------------
mul*.w #6,dx -> add.l dx,dx
		move.l dx,ds
		add.l dx,dx
		add.l ds,dx
------------------------------------
mul*.w #7,dx -> move.l dx,ds
		asl.l #3,dx
		sub.l ds,dx
------------------------------------
mul*.w #9,dx -> move.l dx,ds
		asl.l #3,dx
		add.l ds,dx
------------------------------------
mul*.w #10,dx -> add.l dx,dx
		 move.l dx,ds
		 asl.l #2,dx
		 add.l ds,dx
------------------------------------
mul*.w #12,dx -> asl.l #2,dx
		 move.l dx,ds
		 add.l dx,dx
		 add.l ds,dx
------------------------------------
mulu.w #12,dx -> swap dx	; HEI! spesso occorre azzerare la word 
		 clr.w dx	; alta per i MULU... considerate questo anche
		 swap dx	; per i mulu #3, #5, #6....

		 asl.l #2,dx	; normale mulu #12
		 move.l dx,ds
		 add.l dx,dx
		 add.l ds,dx
------------------------------------

Se dovete azzerare la word alta dei registri molte volte, potete usare anche:

	move.l	#$0000FFFF,ds	; serve 1 registro per tenere $FFFF

	and.l	ds,dx		; questo  pi veloce dello swappaggio, ma
				; richiede un registro che contenga $0000FFFF,
				; altrimenti "AND.L #$FFFF,dx" non  pi
				; veloce...

In sintesi, ricordatevi che in caso di MULS, dato che  SIGNED, pu essere
necessario eseguire un "EXT.L" all'inizio. Invece, nel caso dei MULU, pu 
essere necessario azzerare la word alta del registro.

Ora degli scambi "composti":

asl.x #2,dy -> add.x dy,dy
	       add.x dy,dy
------------------------------------
asl.l #16,dx -> swap dx
		clr.w dx
------------------------------------
asl.w #2,dy -> add.w dy,dy
	       add.w dy,dy
------------------------------------
asl.x #1,dy -> add.x dy,dy
------------------------------------
asr.l #16,dx -> swap dx
		ext.l dx
------------------------------------
bsr label -> bra label
rts
------------------------------------
clr.x n(ax,rx) -> move.x ds,n(ax,rx)	; ds deve essere 0, naturalmente!
------------------------------------
lsl.l #16,dx -> swap dx
		clr.w dx
------------------------------------
move.b #-1,(ax) -> st (ax)
------------------------------------
move.b #-1,dest -> st dest
------------------------------------
move.b #x,mn   -> move.w #xy,mn
move.b #y,mn+1
------------------------------------
move.x ax,ay -> lea n(ax),ay		; -32767 <= n <= 32767
add.x #n,ay
------------------------------------
move.x ax,az -> lea n(ax,ay),az		;  az=n+ax+ay, n<=32767
add.x #n,az
add.x ay,az
------------------------------------
sub.x #n,ax -> lea -n(ax),ax		; -32767 <= n <= -9, 9 <= n <= 32767
------------------------------------

A questo punto vedetevi il tempo di esecuzione delle varie istruzioni.
Al tempo di esecuzione dell'istruzione, va aggiunto il tempo speso per i
vari indirizzamenti, il cui tempo di esecuzione  stato visto prima.
State attenti al fatto che si tratta dei tempi di esecuzione del normale 68000!
Per esempio, nel 68040 i MULS/MULU sono implementati via hardware e prendono
pochi cicli!

>>>				MOVE.B e MOVE.W				   <<<

+-------------+---------------------------------------------------------------+
|             |                           DESTINAZIONE                        |
+   SORG.     +---------------------------------------------------------------+
|             | Dn | An |(An)|(An)+|-(An)|(d16,An)|(d8,An,Xn)*|(xxx.W)|(xxx).L|
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| Dn / An     | 4  | 4  | 8  |  8  |  8  |   12   |    14     |  12   |  16   |
| (An)        | 8  | 8  | 12 | 12  | 12  |   16   |    18     |  16   |  20   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| (An)+       | 8  | 8  | 12 | 12  | 12  |   16   |    18     |  16   |  20   |
| -(An)       | 10 | 10 | 14 | 14  | 14  |   18   |    20     |  18   |  22   |
| (d16,An)    | 12 | 12 | 16 | 16  | 16  |   20   |    22     |  20   |  24   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| (d8,An,Xn)* | 14 | 14 | 18 | 18  | 18  |   22   |    24     |  22   |  26   |
| (xxx).W     | 12 | 12 | 16 | 16  | 16  |   20   |    22     |  20   |  24   |
| (xxx).L     | 16 | 16 | 20 | 20  | 20  |   24   |    26     |  24   |  28   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| (d16,PC)    | 12 | 12 | 16 | 16  | 16  |   20   |    22     |  20   |  24   |
| (d8,PC,Xn)* | 14 | 14 | 18 | 18  | 18  |   22   |    24     |  22   |  26   |
| #(data)     | 8  | 8  | 12 | 12  | 12  |   16   |    18     |  16   |  20   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
* La grandezza del registro indice (Xn) (.w o .l) non cambia la velocit.


>>>				    MOVE.L			   <<<

+-------------+---------------------------------------------------------------+
|             |                           DESTINAZIONE                        |
+   SORG.     +---------------------------------------------------------------+
|             | Dn | An |(An)|(An)+|-(An)|(d16,An)|(d8,An,Xn)*|(xxx.W)|(xxx).L|
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| Dn o  An    | 4  | 4  | 12 | 12  | 12  |   16   |    18     |  16   |  20   |
| (An)        | 12 | 12 | 20 | 20  | 20  |   24   |    26     |  24   |  28   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| (An)+       | 12 | 12 | 20 | 20  | 20  |   24   |    26     |  24   |  28   |
| -(An)       | 14 | 14 | 22 | 22  | 22  |   26   |    28     |  26   |  30   |
| (d16,An)    | 16 | 16 | 24 | 24  | 24  |   28   |    30     |  28   |  32   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| (d8,An,Xn)* | 18 | 18 | 26 | 26  | 26  |   30   |    32     |  30   |  34   |
| (xxx).W     | 16 | 16 | 24 | 24  | 24  |   28   |    30     |  28   |  32   |
| (xxx).L     | 20 | 20 | 28 | 28  | 28  |   22   |    34     |  32   |  36   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
| (d,PC)      | 16 | 16 | 24 | 24  | 24  |   28   |    30     |  28   |  32   |
| (d,PC,Xn)*  | 18 | 18 | 26 | 26  | 26  |   30   |    32     |  30   |  34   |
| #(data)     | 12 | 12 | 20 | 20  | 20  |   24   |    26     |  24   |  28   |
+-------------+----+----+----+-----+-----+--------+-----------+-------+-------+
* La grandezza del registro indice (Xn) (.w o .l) non cambia la velocit.

Ed ora le altre istruzioni.
Note:

#  - Operando Immediato
An - Registro indirizzi
Dn - Registro Dati
ea - Un operando specificato da un Indirizzo effettivo
M  - Indirizzo effettivo
+  - Aggiungere il tempo speso a calcolare l'indirizzo (indirizzmento)

+-------------+-----------+------------+-----------+-----------+
| Istruzione  |   Size    | op<ea>,An | op<ea>,Dn | op Dn,<M> |
+-------------+-----------+------------+-----------+-----------+
|             | Byte,Word |     8+     |     4+    |     8+    |
|  ADD/ADDA   +-----------+------------+-----------+-----------+
|             |   Long    |     6+     |     6+    |    12+    |
+-------------+-----------+------------+-----------+-----------+
|             | Byte,Word |     -      |     4+    |     8+    |
|  AND        +-----------+------------+-----------+-----------+
|             |   Long    |     -      |     6+    |    12+    |
+-------------+-----------+------------+-----------+-----------+
|             | Byte,Word |     6+     |     4+    |     -     |
|  CMP/CMPA   +-----------+------------+-----------+-----------+
|             |   Long    |     6+     |     6+    |     -     |
+-------------+-----------+------------+-----------+-----------+
|  DIVS       |     -     |     -      |   158+    |     -     |
+-------------+-----------+------------+-----------+-----------+
|  DIVU       |     -     |     -      |   140+    |     -     |
+-------------+-----------+------------+-----------+-----------+
|             | Byte,Word |     -      |     4     |     8+    |
|  EOR        +-----------+------------+-----------+-----------+
|             |   Long    |     -      |     8     |    12+    |
+-------------+-----------+------------+-----------+-----------+
|  MULS/MULU  |     -     |     -      |    70+    |     -     |
+-------------+-----------+------------+-----------+-----------+
|             | Byte,Word |     -      |     4+    |     8+    |
|  OR         +-----------+------------+-----------+-----------+
|             |   Long    |     -      |     6+    |    12+    |
+-------------+-----------+------------+-----------+-----------+
|             | Byte,Word |     8+     |     4+    |     8+    |
|  SUB        +-----------+------------+-----------+-----------+
|             |   Long    |     6+     |     6+    |    12+    |
+-------------+-----------+------------+-----------+-----------+

+-------------+-----------+---------+---------+--------+
| Istruzione  |   Size    | op #,Dn | op #,An | op #,M |
+-------------+-----------+---------+---------+--------+
|             | Byte,Word |    8    |    -    |   12+  |
|  ADDI       +-----------+---------+---------+--------+
|             |   Long    |    16   |    -    |   20+  |
+-------------+-----------+---------+---------+--------+
|             | Byte,Word |    4    |    4    |    8+  |
|  ADDQ       +-----------+---------+---------+--------+
|             |   Long    |    8    |    8    |   12+  |
+-------------+-----------+---------+---------+--------+
|             | Byte,Word |    8    |    -    |   12+  |
|  ANDI       +-----------+---------+---------+--------+
|             |   Long    |   14    |    -    |   20+  |
+-------------+-----------+---------+---------+--------+
|             | Byte,Word |    8    |    -    |    8+  |
|  CMPI       +-----------+---------+---------+--------+
|             |   Long    |   14    |    -    |   12+  |
+-------------+-----------+---------+---------+--------+
|             | Byte,Word |    8    |    -    |   12+  |
|  EORI/SUBI  +-----------+---------+---------+--------+
|             |   Long    |   16    |    -    |   20+  |
+-------------+-----------+---------+---------+--------+
|  MOVEQ      |   Long    |    4    |    -    |   -    |
+-------------+-----------+---------+---------+--------+
|             | Byte,Word |    8    |    -    |   12+  |
|  ORI        +-----------+---------+---------+--------+
|             |   Long    |   16    |    -    |   20+  |
+-------------+-----------+---------+---------+--------+
|             | Byte,Word |    4    |    8    |    8+  |
|  SUBQ       +-----------+---------+---------+--------+
|             |   Long    |    8    |    8    |   12+  |
+-------------+-----------+---------+---------+--------+

+-------------+-----------+----------+--------+
| Istruzione  |   Size    | Register | Memory |
+-------------+-----------+----------+--------+
|  NBCD       |   Byte    |    6     |    8+  |
+-------------+-----------+----------+--------+
|             | Byte,Word |    4     |    8+  |
|  CLR/NEG    +-----------+----------+--------+
|  NEGX/NOT   |   Long    |    6     |   12+  |
+-------------+-----------+----------+--------+
|             | Byte,False|    4     |    8+  |
|  Scc        +-----------+----------+--------+
|             | Byte,True |    6     |    8+  |
+-------------+-----------+----------+--------+
|  TAS        |   Byte    |    4     |   14+  |
+-------------+-----------+----------+--------+
|  TST   | Byte,Word,Long |    4     |    4+  |
+-------------+-----------+----------+--------+
|  LSR/LSL    | Byte,Word |  6 + 2n  |   8+   |
|  ASR/ASL    +-----------+----------+--------+
|  ROR/ROL    |   Long    |  8 + 2n  |   -    |
|  ROXR/ROXL  |           |          |        |
+-------------+-----------+----------+--------+
nota: n  il numero di shifts!

Bit Manipulation Istruzione Execution Times
+-------------+-----------+-------------------+-------------------+
|             |           |       Dynamic     |       Static      |
| Istruzione  |   Size    +----------+--------+----------+--------+
|             |           | Register | Memory | Register | Memory |
+-------------+-----------+----------+--------+----------+--------+
|             |   Byte    |    -     |   8+   |    -     |  12+   |
|  BCHG/BSET  +-----------+----------+--------+----------+--------+
|             |   Long    |    8     |   -    |    12    |   -    |
+-------------+-----------+----------+--------+----------+--------+
|             |   Byte    |    -     |   8+   |    -     |  12+   |
|  BCLR       +-----------+----------+--------+----------+--------+
|             |   Long    |   10     |   -    |    14    |   -    |
+-------------+-----------+----------+--------+----------+--------+
|             |   Byte    |    -     |   4+   |    -     |   8+   |
|  BTST       +-----------+----------+--------+----------+--------+
|             |   Long    |    6     |   -    |    10    |   -    |
+-------------+-----------+----------+--------+----------+--------+

+-------------+-------------------+--------+-----------+
|             |                   | Branch |  Branch   |
| Istruzione  |   Displacement    | Taken  | Not Taken |
+-------------+-------------------+--------+-----------+
|             |       Byte        |   10   |     8     |
|  Bcc        +-------------------+--------+-----------+
|             |       Word        |   10   |    12     |
+-------------+-------------------+--------+-----------+
|             |       Byte        |   10   |     -     |
|  BRA        +-------------------+--------+-----------+
|             |       Word        |   10   |     -     |
+-------------+-------------------+--------+-----------+
|  BSR        |     Byte,word     |   18   |     -     |
+-------------+-------------------+--------+-----------+
|             |      cc true      |   -    |    12     |
|             +-------------------+--------+-----------+
|             |  cc false, Count  |        |     _     |
|  DBcc       |    Not Expired    |   10   |           |
|             +-------------------+--------+-----------+
|             | cc false, Counter |   _    |           |
|             |      Expired      |        |    14     |
+-------------+-------------------+--------+-----------+

+----+----+---+-----+-----+--------+-----------+------+-------+-------+-------+
|Ins.|Sz|(An)|(An)+|-(An)|(d16,An)|(d8,An,Xn)+|(x).W|(x).L|(d16,PC)|(d8,PC,Xn)*
+----+---+----+-----+-----+-------+-----------+-----+-----+--------+----------+
| JMP| -  | 8  |  -  | -  |  10   |   14      | 10  | 12  |  10    |    14    |
+----+----+----+-----+----+-------+-----------+-----+-----+--------+----------+
| JSR| -  | 16 |  -  | -  |  18   |   22      | 18  | 20  |  18    |    22    |
+----+----+----+-----+----+-------+-----------+-----+-----+--------+----------+
| LEA| -  | 4  |  -  | -  |  8    |   12      |  8  | 12  |  8     |    12    |
+----+----+-----+-----+----+------+-----------+-----+-----+--------+----------+
| PEA| -  | 12  |  -  | -  |  16  |   20      | 16  | 20  |  16    |    20    |
+-----+----+-----+-----+----+-----+-----------+-----+-----+--------+----------+
|     |Word|12+4n|12+4n| _  |16+4n|  18+4n    |16+4n|20+4n| 16+4n  |  18+4n   |
|     |    |     |     |    |     |           |     |     |        |          |
|MOVEM+----+-----+-----+----+-----+-----------+-----+-----+--------+----------+
|M->R |Long|12+8n|12+8n| _  |16+8n|  18+8n    |16+8n|20+8n| 16+8n  |  18+8n   |
|     |    |     |     |    |     |           |     |     |        |          |
+-----+----+-----+-----+----+-----+-----------+-----+-----+--------+----------+
|     |Word| 8+4n|  _  |8+4n|12+4n|  14+4n    |12+4n|16+4n|   _    |    _     |
|     |    |     |     |    |     |           |     |     |        |          |
|MOVEM+----+-----+-----+----+-----+-----------+-----+-----+--------+----------+
|R->M |Long| 8+8n|  _  |8+8n|12+8n|  14+8n    |12+8n|16+8n|   _    |    _     |
|     |    |     |     |    |     |           |     |     |        |          |
+-----+----+-----+-----+----+-----+-----------+-----+-----+--------+----------+
nota: n  il numero di registri da muovere.


EXT/SWAP/NOP	4
EXG		6
UNLK		12
LINK/RTS	16
RTE		20

Considerate infine che le exceptions richiedono 44 cicli se si tratta di
un interrupt, 34 se si tratta di un TRAP. Pi 20 per l'RTE!!!
Mi raccomando di commentare SEMPRE una ottimizzazione, ad esempio, supponiamo
vogliate ottimizzare questa routine:

	movem.l	label1(PC),d1-d4
	mulu.w	#16,d1
	mulu.w	#3,d2
	muls.w	#5,d3
	divu.w	#8,d4
	rts

Ottimizzandola, il risultato sarebbe:

	movem.l	label1(PC),d1-d4
	asl.l	#4,d1		; mulu.w #16,d1
	move.l	d2,d5		; \
	add.l	d2,d2		;  > mulu.w #3,d2
	add.l	d5,d2		; /
	move.l	d3,d5		; \
	asl.l	#2,d3		;  > muls.w #5,d3
	add.l	d5,d3		; /
	asr.l	#3,d4		; divu.w #8,d4
	rts

Oltre ad aver usato il registro d5, abbiamo reso pi difficile da leggere
il listato. A prima vista, se non avessimo messo i commenti, si capirebbe
cosa succede ai registri d1,d2,d3 e d4? E immaginatevi se avessimo dovuto
anche pulire la word alta prima dei MULU e estendere prima dei MULS:

	movem.l	label1(PC),d1-d4
	swap	d1
	clr.w	d1
	swap	d1
	asl.l	#4,d1
	swap	d2
	clr.w	d2
	swap	d2
	move.l	d2,d5
	add.l	d2,d2
	add.l	d5,d2
	ext.l	d3
	move.l	d3,d5
	asl.l	#2,d3
	add.l	d5,d3
	asr.l	#3,d4
	rts

Oppure, si pu azzerare la word alta nel modo pi veloce:

	move.l	#$FFFF,d6
	...
	movem.l	label1(PC),d1-d4
	and.l	d6,d1
	asl.l	#4,d1
	and.l	d6,d2
	move.l	d2,d5
	add.l	d2,d2
	add.l	d5,d2
	ext.l	d3
	move.l	d3,d5
	asl.l	#2,d3
	add.l	d5,d3
	asr.l	#3,d4
	rts

Se tornate al vostro listato, dopo 1 mese dalla scrittura, quanto ci
mettereste a capire che tutte queste istruzioni incomprensibili non fanno
altro che 3 moltiplicazioni e una divisione? CI METTERESTE MOLTO, o dovreste
addirittura cancellare il listato e ripartire da capo in caso di modifica.
Non ho messo i commenti a quest'ultima versione proprio per farvi capire
come sia FONDAMENTALE mettere i commenti alle ottimizzazioni, come nel
listato precedente. Quindi: COMMENTATE SEMPRE LE OTTIMIZZAZIONI!!!!!!!!!!!!

Altro esempio: vedetevi queste 3 istruzioni:

	move.l	a1,a0
	add.w	#80,a0
	add.l	d0,a0

La stessa cosa si pu fare con:

	lea	80(a1,d0.l),a0	; oppure d0.w se basta la word bassa di d0.

*****************************************************************************
* OTTIMIZZAZIONI DI SECONDO LIVELLO: IL "TABELLAMENTO" -> PRECALCOLO!       *
*****************************************************************************

Parliamo ora delle tabelle, un argomento tra i pi importanti per
l'Ottimizzazione, quella con la O maiuscola, che permette di andare pi
veloci di qualsiasi compilatore C, BASIC, eccetera.
Le tabelle per l'ottimizzazione sono "simili" a quelle usate per contenere
le coordinate dell'ondeggiamento degli sprite o altro, che abbiamo visto
nelle lezioni precedenti: in quel caso si pu dire che "precalcolavamo" le
varie posizioni che avrebbero assunto gli oggetti, ma qua la tabella viene
usata per "precalcolare" i risultati di una data moltiplicazione, divisione,
o di intere funzioni matematiche, quindi il caso  un p diverso.
Facciamo un esempio concreto.
Supponiamo di avere una routine che elebora una serie di valori copresi tra 0
e 100, ed a un certo punto bisogna eseguire una moltiplicazione per una
costante c. Ora, se quella routine deve essere eseguita molte volte, allora
quella moltiplicazione ci far perdere un bel p di tempo.
Come aggirare il problema ? Ci creiamo una tabella contenete tutti i valori
del nostro "range" (serie) da 0..100 gi moltiplicati per c, cio una cosa
del genere:

Table:
	dc.w	0*c
	dc.w	1*c
	dc.w	2*c
	dc.w	3*c
	.
	dc.w	n*c
	.
	dc.w	100*c

A questo punto  facile accedere alla tabella, perch dato il valore
da moltiplicare per c in d0, avremo che:

	Lea	Table,a0	; Indirizzo della tabella
	Add.w	d0,d0		; d0 * 2, per trovare l'offset nella tabella,
				; dato che ogni suo valore  lungo 1 word.
	Move.w	(a0,d0.w),d0	; Copia il valore giusto dalla tabella in d0

Facile, no? L'unico svantaggio  che abbiamo il listato 100 words pi lungo,
per contenere la tabella. Se tale tabella poi non fosse pi lontana di 256
bytes, potremmo scrivere:

	Add.w	d0,d0			; d0*2, ogni val. 1 word, ossia 2 byte
	Move.w	Table(pc,d0.w),d0	; copia dalla tab. il valore giusto

Se il listato fosse per 68020+, basterebbe una sola istruzione:

	Move.w	Table(pc,d0.w*2),d0	; istruzione del 68020 o superiori

Quest'ultima per  una anticipazione, infatti le ottimizzazioni specifiche
per 68020 le tratteremo in seguito.
Comunque, la soluzione pi adottata per tabelle "corte"  quella di
costruirsele in una section BSS tramite una routine. In questo modo, il
file eseguibile non risulta pi lungo, ma occupa solamente un poco pi
di memoria (a meno che non facciate una tabella lunga 500Kb, in tal caso
occupa MOLTA pi memoria, heheheeh!)

Se siete stati attenti, nelle lezioni precedenti abbiamo gi "tabellato" un
paio di listati: uno per togliere una "MULU.W #40", molto frequente dato che
40  la lunghezza di una linea di schermo lowres. Rivedetevi attentamente
quell'esempio, si tratta di Lezione8n2.s, dove sono presenti sia la versione
ottimizzata che quella normale a confronto. Rivedetevi anche i listati
precedenti per vedere le routine normale e ottimizzata da sole.
Il problema era un:

	mulu.w	#largschermo,d1		; Ossia mulu.w #40,d1

Per "risolverlo", ecco lo stratagemma:

; PRECALCOLIAMO UNA TABELLA CON I MULTIPLI DI 40, ossia della larghezza dello
; schermo, per evitare di fare una moltiplicazione per ogni plottaggio.

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

	lea	MulTab,a0	; Indirizzo spazio di 256 words dove scrivere
				; i multipli di 40...
	moveq	#0,d0		; Iniziamo da 0...
	move.w	#256-1,d7	; Numero di multipli di 40 necessari
PreCalcLoop
	move.w	d0,(a0)+	; Salviamo il multiplo attuale
	add.w	#LargSchermo,d0	; aggiungiamo larghschermo, prossimo multiplo
	dbra	d7,PreCalcLoop	; Creiamo tutta la MulTab
	....

	SECTION	Precalc,bss

MulTab:
	ds.w	256	; notare che la section bss, composta da zeri, non
			; allunga la lunghezza effettiva del file eseguibile.

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

Questo per quanto riguarda il calcolo della tabella. Poi, al posto del mulu:

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

	lea	MulTab,a1	; Indirizzo della tabella con i multipli della
				; largh. schermo precalcolati in a1
	add.w	d1,d1		; d1*2, per trovare l'offset nella tabella
	add.w	(a1,d1.w),d0	; copia il multiplo giusto da tab a d0

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

Questo, in breve,  il metodo per tabellare una moltiplicazione.
Naturalmente, qua sapevamo che d1 poteva andare solo da 0 a 255, di
conseguenza abbiamo precalcolato solo 256 multipli. Se invece d1 avesse
avuto un range da 0 a 65000, avremmo dovuto fare una tabella lunga 128Kb,
e ci potrebbe anche non convenire!
Se il risultato massimo nella tabella non supera $FFFF, ossia 65535, basta
fare una tabella con valori .Word. Se invece i valori pi alti superano
tale valore, la tabella deve essere fatta di longword. In questo caso,
dovremo cambiare il modo di trovare l'offset: non pi *2, ma *4!

	lea	MulTab,a1	; Indirizzo della tabella con i multipli della
				; largh. schermo precalcolati in a1
	add.w	d1,d1		; d1*4, per trovare l'offset nella tabella
	add.w	d1,d1		;
	move.l	(a1,d1.w),d0	; copia il multiplo giusto da tab a d0


Per quanto riguarda il tabellamento delle divisioni, la cosa  analoga,
basta farsi una routine con un loop che divide ogni ciclo per un numero
crescente e salvare i risultati nella tabella. In questo caso si pu
scegliere di salvare solo la word bassa, con il risultato, o anche quella
alta con il resto, se serve al nostro scopo.

Una cosa fondamentale  di crearsi "in loco" la tabella, MAI INSERIRE UNA
TABELLA, SPECIALMENTE SE LUNGA DIVERSI KB, GI CALCOLATA.
Per esempio, se precalcolassimo una multab da 20KB, immaginatevi la differenza
tra un eseguibile che la calcola alla startup, e una che la ha inclusa con
incbin o include gi precalcolata: (esempio)

	file1	->	lunghezza = 40K		; calcola la tab all'inizio
	file1	->	lunghezza = 60K		; ha la tab inclusa con incbin

A livello di consumo della memoria sono in parit, ma se doveste fare una
40K intro o una 64K intro immaginatevi l'immenso risparmio di spazio, a
scapito di 1 secondo o 2 di precalcolamento all'inizio.
Ma anche se aveste fatto un gioco o un programma, il fatto di essere pi
costo di 20k (o pi) vi permetterebbe di inserire pi cose nel disco e
una maggior diffusione nelle bbs data la sua brevit.
Poi c' ancora un incentivo a precalcolare sul posto le tabelle: il fatto che
si pu facilmente modificare il listato, ad esempio volendo moltiplicare per
80 anzich per 40. Il FESSO che ha incluso con l'INCBIN una tabellona di
multipli di 40, dovrebbe riscriversi la routine di moltiplicazione per 80,
eseguirla, salvare il file binario, mentre il FURBO che ha la routine di
creazione nel listato deve cambiare semplicemente 40 in 80, e fa tutto da se.
Infine, specialmente per precalcolamenti di routine complesse, appare MOLTO
pi chiaro il funzionamento se si ha sott'occhio la routine originaria che
crea la tabella. Quindi, PRECALCOLATE SEMPRE "SUL POSTO" LE TABELLE IN SPAZI
AZZERATI, SPECIALMENTE IN SECTION BSS SE SONO TABELLE GRANDI.

Il consiglio che vi posso dare  cercare sempre di tabellare TUTTO.

Se siete stati attentissimi, dovreste ricordarvi anche che nella lezione11 un
listato ha subito una ottimizzazione da tabellamento, ben pi azzardata di
quella vista ora. Infatti si tabella un'intera routine, anzich una sola
moltiplicazione. Non a caso l'ho messa nella Lezione11 e non nella 8!
Il listato "normale"  Lezione11l5.s, quella "tabellata" Lezione11l5b.s
Rivedetevi come  avvenuta la forte ottimizzazione, che ripropongo.

Questa  la routine "normale":

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

Animloop:
	moveq	#0,d0
	move.b	(A0)+,d0	; Prossimo byte in d0
	MOVEQ	#8-1,D1		; 8 bit da controllare e espandere.
BYTELOOP:
	BTST.l	D1,d0		; Testa il bit del loop attuale
	BEQ.S	bitclear	;  azzerato?
	ST.B	(A1)+		; Se no, setta il byte (=$FF)
	BRA.S	bitset
bitclear:
	clr.B	(A1)+		; Se  azzerato, azzera il byte
bitset:
	DBRA	D1,BYTELOOP	; Controlla ed espandi tutti i bit del byte
	DBRA	D7,Animloop	; Converti tutto il fotogramma

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

Non si  fatto altro che precalcolare tutte le possibilita:

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

****************************************************************************
; Routine che precalcola tutti i possibili 8 bytes abbinati ai possibili
; 8 bit. Per tutti si intende $FF, ossia 255.
****************************************************************************

PrecalcoTabba:
	lea	Precalctabba,a1	; Destinazione
	moveq	#0,d0		; Parti dal valore zero
FaiTabba:
	MOVEQ	#8-1,D1		; 8 bit da controllare e espandere.
BYTELOOP:
	BTST.l	D1,d0		; Testa il bit del loop attuale
	BEQ.S	bitclear	;  azzerato?
	ST.B	(A1)+		; Se no, setta il byte (=$FF)
	BRA.S	bitset
bitclear:
	clr.B	(A1)+		; Se  azzerato, azzera il byte
bitset:
	DBRA	D1,BYTELOOP	; Controlla ed espandi tutti i bit del byte:
				; D1 calando ogni volta fa fare il btst di
				; tutti i bit.
	ADDQ.W	#1,D0		; Prossimo valore
	CMP.W	#256,d0		; Li abbiamo fatti tutti? (max $FF)
	bne.s	FaiTabba
	rts

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

E cambiare la routine "esecutiva":

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

Animloop:
	moveq	#0,d0
	move.b	(A0)+,d0	; Prossimo byte in d0
	lsl.w	#3,d0		; d0*8 per trovare il valore nella tabba
				; (ossia l'offset dal suo inizio)
	lea	Precalctabba,a2
	lea	0(a2,d0.w),a2	; In a2 l'indirizzo nella tabba degli 8 byte
				; giusti per "l'espansione" degli 8 bit.
	move.l	(a2)+,(a1)+	; 4 bytes espansi
	move.l	(a2),(a1)+	; 4 bytes espansi (totale 8 bytes!!)

	DBRA	D7,Animloop	; Converti tutto il fotogramma

-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-	-.-

Come vedete, qua stiamo entrando in un tipo di ottimizzazione che per essere
fatto occorre avere una certa esperienza, e un certo intuito.
Meccanicamente  facile dire: "provo a tabellare tutte le moltiplicazioni
e le divisioni, e metto tutti gli addq e i moveq possibili".
Ma quando ci so trovano davanti routines "strane" tipo quella gi vista
che fa dei btst di tutto un byte e lo espande ad 8 bytes, occorre avere
l'occhio di lince per intuire come ottimizzarla.
 questo occhio di lince che fa la differenza tra una routine 3d che va a
scatti quando gira 10 punti, e una che va al cinquantesimo di secondo pur
ruotandone 8192. E naturalmente non si pu fare un elenco di tutte le
possibili routines con accanto tutte le possibili ottimizzazioni.
Occorre farsi l'occhio di lince vedendo i pochi esempi presentati.

******************************************************************************
*		OTTIMIZZAZIONI VARIE - GRUPPO MISTO			     *
******************************************************************************

Consideriamo il caso in cui dobbiamo eseguire una determinata routine per ogni
valore in d0, ed inoltre supponiamo che questi possibili valori siano compresi
tra 0 e 10. Ebbene, potremmo essere tentati di fare una cosa del genere:

	Cmp.b	#1,d0
	Beq.s	Rout1
	Cmpi.b	#2,d0
	Beq.s	Rout2
	...
	Cmp.b	#10,d0
	Beq.s	Rout10

 una pessima idea, infatti come minimo avremmo potuto fare cos:

	Subq.b	#1,d0	; togliamo 1. Se d0=0, allora si imposta il flag Z
	Beq.s	Rout1	; Di conseguenza d0 era 1, e saltiamo a Rout1
	Subq.b	#1,d0	; eccetera...
	Beq.s	Rout2
	...
	Subq.b	#1,d0
	Beq.s	Rout10

In effetti cos  gi meglio, ma noi siamo perfezionisti e con l'aiuto
di una tabella facciamo cos:

	Add.w	d0,d0		  ;\ d0*4, per trovare l'offset nella tabella,
	Add.w	d0,d0		  ;/       costituito da longwords (4 bytes!)
	Move.l	Table(pc,d0.w),a0 ; In a0 l'indirizzo della routine giusta
	Jmp	(a0)

Table:
	dc.l	Rout1	; 0 (valore in d0 per richiamare la routine)
	dc.l	Rout2	; 1
	dc.l	Rout3	; 2
	dc.l	Rout4	; 3
	dc.l	Rout5	; 4
	dc.l	Rout6	; 5
	dc.l	Rout7	; 6
	dc.l	Rout8	; 7
	dc.l	Rout9	; 8
	dc.l	Rout10	; 9

In questo modo non facciamo confronti, ed  ovvio che  un'ottima tecnica
nel caso in cui conosciamo i valori da confronare e sono consecutivi.
Vorrei farvi notare inoltre che se utiliziamo in modo intensivo le tabelle
potremmo addirittura lavorare con le potenze del due, risparmiandoci quindi
quelle due Add.w. Quindi, quando si vuole la routine 1, occorrer d0=0,
quando si vuole Rout2 d0=4, quando si vuole Rout3 d0=8, eccetera.

Ci sono anche delle varianti di questo sistema, ad esempio:

	move.b	Table(pc,d0.w),d0	; Prendi dalla tavola l'offset giusto
	jmp	Table(pc,d0)		; aggiungilo a Table, e salta!

Table:	
	dc.b	Rout1-Table	; 0
	dc.b	Rout2-Table	; 1
	dc.b	Rout3-Table	; 2
	...
	even

Con questo sistema non dobbiamo moltiplicare d0, perch abbiamo fatto una
tabella di offset delle routines dalla tabella stessa.
Qua sono offset .byte, perch si suppone che le routines siano piccole e
vicine. Altrimenti gli offset potranno essere .word:

	add.w	d0,d0			; d0*2
	move.w	Table(pc,d0.w),d0	; Prendi dalla tavola l'offset giusto
	jmp	Table(pc,d0)		; aggiungilo a Table, e salta!

Table:	
	dc.w	Rout1-Table	; 0
	dc.w	Rout2-Table	; 1
	dc.w	Rout3-Table	; 2
	...

Il vantaggio di questo sistema  che non occorre moltiplicare per 4 il
registro d0, ma solo per 2.
Se non riuscite a mettere la tabella abbastanza vicina, potete fare cos:

	add.w	d0,d0			; d0*2
	lea	Table(pc),a0
	move.w	(a0,d0.w),d0
	jmp	(a0,d0.w)

Table:	
	dc.w	Rout1-Table	; 0
	dc.w	Rout2-Table	; 1
	dc.w	Rout3-Table	; 2
	...

Gi che abbiamo implementato il salto alle routine utilizzando Subq.b #1,d0
seguito dai BEQ, senza n CMP n TST, occupiamoci degli usi di tale
particolarit, legata ai Condition Codes (rivedeteveli bene in 68000-2.txt)
Noi programmatori in assembly ci possiamo prendere il lusso di testare tre
condizioni alla volta, infatti consideriamo l'esempio:

	Add.w	#x,d0		; i cc sono settati in qualche modo
	Beq.s	Zero		; il risultato  zero
	Blt.s	Negativo	; il risultato  minore di zero
	...			; Altrimenti il risultato  positivo...

Quindi, se dovete testare qualche risultato, cercate sempre di farlo dopo
l'ultima operazione matematica, e non alla fine quando i cc indicheranno
tutt' altra cosa. Sarebbe bene se conosceste quali cc influenzano le varie
istruzioni.
Inoltre vi consiglio di piazzare le Bcc a seconda della loro probabilit
di essere eseguite per prime, cio praticamente quelle che fanno trasferire
il controllo con pi probabilit.
Ad esempio un altro caso interessante  questo: abbiamo un certo numero di
valori, non sappiamo quanti, ma sappiamo che terminano con uno zero...
supponiamo che dobbiamo copiarli da una zona di memoria ad un altra.
Potremmo fare una cosa del genere:

	Lea	Source,a0
	Lea	Dest,a1
CpLoop:
	Move.b	(a0)+,d0	; sorgente -> d0
	Move.b	d0,(a1)+	; d0 -> destinazione
	Tst.b	d0		; d0=0?
	Bne.s	CpLoop		; Se non ancora, continua

Ma possiamo fare di meglio nel seguente modo:

	Lea	Source,a0
	Lea	Dest,a1
CpLoop:
	Move.b	(a0)+,(a1)+	; sorgente -> destinazione
	Bne.s	CpLoop		; flag 0 settato? Se non ancora, continua!

Come potete vedere, il 68000 in questo caso fa tutto da solo.

Parliamo ora delle chiamate alle soubrutine, e quindi delle Movem.
L'utilizzo delle soubroutine  evidentemente utilissimo nella stesura dei
programmi, ma al momento di ottimizzare il vostro codice occorre notare che
invece di utilizzare la coppia di istruzioni BSR label/RTS potreste anche
utilizzare BRA label, seguita alla fine della soubroutine con una un'altra
BRA che vi riporta alla istruzione immediatamente successiva a JMP label, ma
questa ottimizzione  a vostra discrezione.
Comunque utilizzate se potete sempre BSR invece di JSR ed analogamente BRA
invece di JMP, sempre che sia possibile.
Ritornando comunque all'utilizzo delle routine, capita spesse volte di dover
azzerare i contenuti dei registri prima di iniziare a lavora su di essi,
tuttavia possiamo risparmiarci ogni volta una sfilza di "Moveq #0,Dx" e
"Sub.l Ax,Ax", infatti facciamo questa operazione all'inizio del programma
principale e vediamo cosa accade quando chiamiamo le nostre soubroutine,
esempio:

	Moveq	#0,d0	;
	Moveq	#0,d1
	...
	Moveq	#0,d7
	Move.l	d0,a0
		..
     	Move.l	d0,a6
Main:
	Bsr.s	Pippo
	Bsr.s	Pluto
	Bsr.s	Paperino
	...
	Bra.s	Main

Ebbene se salviamo ad ogni chiamata i contenuti dei registri utilizzati
avremo che ogni volta che termina una routine si andr alla prossima gi
con i registri "puliti",  ovvio che comunque bisogna organizzare il
proprio codice per bene. Altrimenti, potreste con 1 sola istruzione pulire
tutti i registri, e precisamente:

	movem.l	TantiZeri(PC),d0-d7/a0-a6

TantiZeri:
	dcb.b	15,0

Veniamo ora all'istruzione Movem ed esaminiamo i suoi pregi e difetti.
Osserviamo innanzitutto il numero di cicli macchina della Movem, sopratutto
nei trasferimenti di longword: nei trasferimenti dai registri alla memoria
impiega 8+8n, dove n indica il numero di registri, osserviamo inoltre il
numero di cicli che invece impiega una semplice Move.l Dx,(Ax): 12 cicli. 
Il solito ingegnere si potrebbe porre allora la seguente domanda: nel
caso devo trasferire pi longoword contenute in registri tutti differenti,
fino a che punto mi conviene utilizzare la classica Move.l Dx,(Ax) ?
Ebbene anche questa volta l'ingegnere a fatto una giusta osservazione,
consideriamo infatti un caso limite in cui dobbiamo traferire il contenuto
dei registri D0..D7 e A0..A6: avremmo bisogno esattamente di 8+7=15 Move.l
per un totale di 15*12=180 cicli.
Invece, se utiliziamo la Movem, avremmo 8+8*15=128 cicli, cio un risparmio
di 52 cicli!
 evidente a questo punto che bisogna utilizzare la mastodontica Movem quando
bisogna trasferire grosse quantit di dati, tuttavia nel caso siano coinvolti
solamente due registri si pu utilizzare ancora la Move.l normale.
Vediamo a questo punto una serie di applicazioni pratiche che partono da un
codice non ottimizzato fino ad arrivare ad uno ottimizzato.
Supponamo per esempio di dover azzerare 1200 byte a partire dalla locazione
Table; i principianti farebbero in questo modo:

	Lea	Table,a0	; 12 cicli
	Move.w	#1200-1,d7	; 8 cicli
CleaLoop:
	Clr.b	(a0)+		; 8 cicli 
	Dbne	d7,CleaLoop

Questo tipo di codice  orrido!! Infatti vediamo quanto impiega...le prime
due istruzioni impiegano 20 cicli, invece la Clr.b dovr essere eseguita
1200 volte quindi 1200*8=9600 cicli, inoltre c' da aggiungere la Dbne
che dovr essere eseguita ben 10*1199=11990 cicli pi 14 alla fine, dunque
ricapitolando 20+9600+11990+14=21624 cicli!!! B tutto ci non merita 
commento. Avremmo almeno potuto fare una cosa del genere:

	Lea	Table,a0
	Move.w	#(1200/4)-1,d7	; numero di bytes diviso 4, per il clr.L!!
Clr:
	Clr.l	(a0)+		; azzeriamo 4 bytes alla volta...
	Dbra	d7,Clr		; e facciamo 1/4 di loops.

Infatti, con una Clr.l, almeno cancelliamo in un colpo 4 byte, e poich
ne dobbiamo cancellare 1200, faremmo 1200/4=300 cicli, risparmiando un bel
p rispetto a prima (fatevi voi i calcoli per piet).
Per ottimizzare ancora di pi, possiamo fare questo:

	Lea	Table,a0
	Move.w	#(1200/16)-1,d7	; numero di bytes diviso 16, per il clr.L!!
Clr:
	Clr.l	(a0)+		; azzeriamo 4 bytes
	Clr.l	(a0)+		; azzeriamo 4 bytes
	Clr.l	(a0)+		; azzeriamo 4 bytes
	Clr.l	(a0)+		; azzeriamo 4 bytes
	Dbra	d7,Clr		; e facciamo 1/16 di loops.

Tuttavia anche questo tipo di codice pu essere classificato come pessimo,
proviamo ad ottimizzarlo di pi, usando un registro dati:

	Lea	Table,a0
	moveq	#0,d0		; pi veloce "move.l d0" di un "CLR"!
	Move.w	#(1200/32)-1,d7	; numero di bytes diviso 32
Clr:
	move.l	d0,(a0)+		; azzeriamo 4 bytes
	move.l	d0,(a0)+
	move.l	d0,(a0)+
	move.l	d0,(a0)+
	move.l	d0,(a0)+
	move.l	d0,(a0)+
	move.l	d0,(a0)+
	move.l	d0,(a0)+
	Dbra	d7,Clr		; e facciamo 1/32 di loops.

Con questa versione abbiamo aumentato l'ottimizzazione dovuta al calo dei
dbra da eseguire, e abbiamo sfruttato il fatto che usare i registri 
megaveloce, anche pi del "CLR".

Veniamo ora ad utilizzare la Movem e vediamo cosa avviene:

	movem.l	TantiZeri(PC),d0-d6/a0-a6	; azzeriamo tutti i registri
						; tranne d7 e a7, naturalmente,
						; che  lo stack. Potete
						; azzerarli cos o con
						; tanti moveq #0,Dx...

; Ora abbiamo 7+7=14 registri azzerati, per un totale di 14*4=56 byte.
; Dobbiamo fare 1200byte/56byte = 21 trasferimenti, ma 21*56 = 1176 byte, e ne
; restano da fare altri 1200-1176 = 24byte che faremo a parte.

	Move.l	a7,SalvaStack	; Salviamo lo stack in una label
	Lea	Table+1200,a7	; Mettiamo in A7 (o SP,  lo stesso registro)
				; l'indirizzo della fine dell'area da pulire.
	Moveq	#21-1,d7	; Numero di movem da fare (2100/56=21)
CleaLoop:
	Movem.l	d0-d6/a0-a6,-(a7) ; Azzeriamo "all'indietro" 56 bytes.
				  ; Se vi ricordate, il movem in scrittura
				  ; lavora "all'indietro", per lo stack.
	Dbra	d7,CleaLoop
	Movem.l	d0-d5,(a7)+	  ; Azzeriamo gli altimi 24 bytes
	Move.l	SalvaStack(PC),a7 ; Rimettiamo a posto lo stack in SP
	rts

SalvaStack:
	dc.l	0

Facciamo un p di conti, la Movem interna occuper esattamente 8+8*14=120
cicli, dovr essere eseguita 21 volte, quindi 21*120=2520 cicli, a cui
dovremo aggiungere tutta la fase di inizializzazione e di chiusura ma non
vi preoccupate non superer mai i casi di sopra. Possiamo essere ancora
pi perfezionisti espandendo il codice, cio eliminando il cicli e 
piazzando tante Movem quante ne abbiamo bisogno; non vi spaventate,
l'espansione del codice  una tecnica molto utilizzata, sopratutto quando
non si s pi cosa ottimizzare, vedremo in seguito una serie di esempi.
Nel caso di prima, comunque, ecco cosa succederebbe:

	Move.l	a7,SalvaStack	; Salviamo lo stack in una label
	Lea	Table+1200,a7	; Mettiamo in A7 (o SP,  lo stesso registro)
				; l'indirizzo della fine dell'area da pulire.
CleaLoop:

	rept	20		  ; ripeto 20 movem...
	Movem.l	d0-d7/a0-a6,-(a7) ; Azzeriamo "all'indietro" 60 bytes.
	endr

	Move.l	SalvaStack(PC),a7 ; Rimettiamo a posto lo stack in SP
	rts

Da notare che, avendo eliminato il dbra, possiamo usare anche il registro d7,
che ci fa azzerare 4 bytes in pi per ogni movem. In questo modo, 1200/60
fa 20 esatto. Le demo solitamente usano questo sistema, il pi veloce!

Vediamo meglio la tecnica di espansione del codice. Osservate questa routine:

ROUTINE2:
	MOVEQ	#64-1,D0	; 64 cicli
SLOWLOOP2:
	MOVE.W	(a2),(a1)
	ADDQ.w	#4,a1
	ADDQ.w	#8,a2
	DBRA	D0,SLOWLOOP2

Ed ecco la routine molto velocizzata:

ROUTINE2:
	MOVE.W	(a2),(a1)
	MOVE.W	8(a2),4(a1)
	MOVE.W	8*2(a2),4*2(a1)
	MOVE.W	8*3(a2),4*3(a1)
	MOVE.W	8*4(a2),4*4(a1)
	MOVE.W	8*5(a2),4*5(a1)
	MOVE.W	8*6(a2),4*6(a1)
	MOVE.W	8*7(a2),4*7(a1)
	.....
	MOVE.W	8*63(a2),4*63(a1)

Abbiamo tolto il tempo usato per il DBRA e i 2 addq!
Comunque, occorre dire che nei processori 68020 e superiori sono presenti le
instruction cache, che velocizzano i loop lunghi meno di 256 bytes.
Quindi, pu succedere di ottimizzare per 68000, e rendere meno veloce su
un 68020. Di conseguenza, sarebbe bene fare una mediazione di questo tipo:

ROUTINE2:
	MOVEQ	#4-1,D0		; solo 4 cicli (64/16)
FASTLOOP2:
	MOVE.W	(a2),(a1)		; 1
	MOVE.W	8(a2),4(a1)		; 2
	MOVE.W	8*2(a2),4*2(a1)		; 3
	MOVE.W	8*3(a2),4*3(a1)		; 4
	MOVE.W	8*4(a2),4*4(a1)		; 5
	MOVE.W	8*5(a2),4*5(a1)		; ...
	MOVE.W	8*6(a2),4*6(a1)
	MOVE.W	8*7(a2),4*7(a1)
	MOVE.W	8*8(a2),4*8(a1)
	MOVE.W	9*9(a2),4*9(a1)
	MOVE.W	8*10(a2),4*10(a1)
	MOVE.W	8*11(a2),4*11(a1)
	MOVE.W	8*12(a2),4*12(a1)
	MOVE.W	8*13(a2),4*13(a1)
	MOVE.W	8*14(a2),4*14(a1)
	MOVE.W	8*15(a2),4*15(a1)	; 16
	ADD.w	#4*16,a1
	ADD.w	#8*16,a2
	DBRA	D0,FASTLOOP2

Questo anche per il clear con i movem e le altre routine dove ripetiamo a
tappeto.

Facciamo ora un paio di osservazione utili:
il metodo di indirizzamento indiretto con autoincremento  qualcosa da
tenere sempre in considerazione. Infatti l'indiretto, sia senza incremento
che con incremento, impiega lo stesso numero di cicli, un ottimo caso
 nell'utilizzo del Blitter, ed un esempio di questo genere lo vedremo in
seguito.
Il secondo metodo che abbiamo utilizzato per copiare i 1200 byte tuttavia non
 proprio da buttare completamente: nel caso dovessimo fare una copia
possiamo fare molto meglio, ma pensate al caso in cui dovessimo mascherare
1200 byte: siamo per forza costretti ad utilizzare un ciclo Dbcc.
In questi casi cercate di abusare dell'istruzione Dbcc e ricordate che su un
680xx dotato di cache questi tipi di cicli sono eseguiti a velocit TURBO.
Inoltre le istruzioni DBcc sono ottime anche per comparare, ecco un esempio:

	Move.w	Len(PC),d0	; Max lunghezza in cui cercare <> 0
	Move.l	String(PC),a0	
	Moveq	#Char,d1	; Carattere da cercare
FdLoop:
	Cmp.b	(a0)+,d1
	Dbne.s	d0,FdLoop

Il seguente ciclo controlla due cose contemporaneamente, infatti i cc EQ
saranno settati se abbiamo esaminato tutti i Len (numero caratteri), oppure
se il carattere  stato trovato, in questo caso saremmo anche in grado di dire
in che posizione si trova.
Vorrei a questo punto fare gli ultimi esempi sulla Movem ed esattamente sulla
copia di zone di memoria: a differenza dell'azzeramento, qui dobbiamo
prelevare dei dati e poi scaricarli, ma vediamo subito un esempio:

	Lea	Start,a0
	Lea	Dest,a1
FASTCOPY:				; Impiego 13 registri
	Movem.l	(a0)+,d0-d7/a2-a6
	Movem.l	d0-d7/a2-a6,(a1)
	Movem.l	(a0)+,d0-d7/a2-a6
	Movem.l	d0-d7/a2-a6,$34(a1)	; $34
	Movem.l	(a0)+,d0-d7/a2-a6
	Movem.l	d0-d7/a2-a6,$34*2(a1)	; $34*2
	Movem.l	(a0)+,d0-d7/a2-a6
	Movem.l	d0-d7/a2-a6,$34*3(a1)
	Movem.l	(a0)+,d0-d7/a2-a6
	Movem.l	d0-d7/a2-a6,$34*4(a1)
	Movem.l	(a0)+,d0-d7/a2-a6
	Movem.l	d0-d7/a2-a6,$34*5(a1)
	Movem.l	(a0)+,d0-d7/a2-a6
	Movem.l	d0-d7/a2-a6,$34*6(a1)
	Movem.l	(a0)+,d0-d7/a2-a6

Innazitutto qui abbiamo adottato la tecnica (se si pu chiamarla cos)
dell'espansione del codice: pu risultare esagerato, ma  molto efficiente.
Bene, cosa abbiamo fatto ? Preleviamo 13*4 byte dalla locazione di memoria
puntata ad a0, e li copiamo nella locazione di memoria puntata in a1, 
prestando attenzione al fatto di aumentare l'offset di a1 dopo ogni
copia. Nel caso in cui vogliate espandere il codice ma, vi da fastidio
vedere tutte quelle istruzioni, potete usare la direttiva rept:

	REPT		100
	And.l		(a0)+,(a1)+	
	ENDR

Sar poi l'assemblatore a generarle per voi. Infine vediamo un esempio
legato ai registri colori:

	Lea	$dff180,a6
	Movem.l	Colours(pc),d0-a5	; carichiamo 14 longword o 28 word
	Movem.l	d0-a5,(a6)		; setta 28 colori in un solo colpo!!
	
Colours:	dc.w	...


Oppure quando all'inizio di una routine dovete caricare molti registri:


	MOVE.L	#$4232,D0
	MOVE.W	#$F20,D1
	MOVE.W	#$7FFF,D2
	MOVEQ	#0,D3
	MOVE.L	#123456,D4
	LEA	$DFF000,A0
	LEA	$BFE001,A1
	LEA	$BFD100,A2
	LEA	Schermo,A3
	LEA	BUFFER,A4
	...

Tutto questo si pu riassumere con 1 sola routine:


	MOVEM.L	VariaRoba(PC),D0-D4/A0-A4
	...

VariaRoba:
	dc.l	$4243		; d0
	dc.l	$f20		; d1
	dc.l	$7fff		; d2
	dc.l	0		; d3
	dc.l	$123456		; d4
	dc.l	$dff000		; a0
	dc.l	$bfe001		; a1
	dc.l	$bfd100		; a2
	dc.l	Schermo		; a3
	dc.l	Buffer		; a4

Sull' istruzione Movem potremmo fare molti altri esempi, ma credo che abbiate
capito la sua convenienza in dati casi.

Le chiamate relative al Program Counter (PC) sono pi veloci di quelle
normali alle label perch sono pi "piccole". Infatti quelle normali
devono contenere l'indirizzo lungo 32bit della label, mentre quelle (PC)
contengono solo l'offset a 16 bit dal registro PC, cosa che risparmia 2
bytes e del tempo. Purtroppo,  proprio il fatto che l'offset sia a 16
bit che non permette di rendere relative al PC label pi lontane di 32k
in avanti o indietro.
Veniamo ora ad un trucchetto per rendere relativo al (PC) tutto il programma,
cosa che velocizza anche l'esecuzione. Come sapete,  possibile fare:

	move.l	label1(PC),d0

Ma  impossibile rendere relativa al PC questa istruzione:

	move.l	d0,label1


Come fare? Non  un problema importantissimo, ma mettiamo che abbiamo in
un loop eseguito molte volte questa istruzione.
Se non possiamo rendere la label relativa al PC, possiamo renderla relativa
ad un comune registro indirizzi!
Il metodo pi evidente,  questo:

	move.x	XXXX,label	->	lea	label(PC),a0
					move.x  XXXX,(a0)

	tst.x	label		->	lea	label(PC),a0
					tst.x	label

Da notare, che si risparmia anche a sostituire i valori #immediati con
valori caricati in registri dati, sempre che i valori siano tra -80 e +7f
per permettere il "moveq":

	move.l	#xx,dest	->	moveq	#xx,d0
					move.l	d0,dest


	ori.l	#xx,dest	->	moveq	#xx,d0
					or.l	d0,dest


	addi.l	#xx,dest	->	moveq	#xx,d0
					add.l	d0,dest

In particolare, se  possibile caricare tutti i registri prima di un loop,
per poi risparmiare sul caricamento, si possono anche fare "MOVE.L #xx,Dx"
tranquillamente, il loop senza #immediati ci ripagher!

Esempio:

RoutineSchifosa:
	move.w	#1024-1,d7		; numero di loops
LoopSquallido:
	add.l	#$567,label2
	sub.l	#$23,label3
	move.l	label2(PC),(a0)+
	move.l	label3(PC),(a0)+
	add.l	#30,(a0)+
	sub.l	#20,(a0)+
	dbra	d7,LoopSquallido
	rts

Questa si pu ottimizzare cos:

RoutineDecente:
	moveq	#30,d0		; carichiamo i registri necessari...
	moveq	#20,d1
	move.l	#$567,d2
	moveq	#$23,d3
	lea	label2(PC),a1
	lea	label3(PC),a2
	move.w	#1024-1,d7		; numero di loops
LoopNormale:
	add.l	d2,(a1)
	sub.l	d3,(a2)
	move.l	(a1),(a0)+
	move.l	(a2),(a0)+
	add.l	d0,(a0)+
	sub.l	d1,(a0)+
	dbra	d7,LoopNormale
	rts

Per esagerare, possiamo infine risparmiare sul numero di dbra da eseguire:

RoutineOK:
	moveq	#30,d0
	moveq	#20,d1
	move.l	#$567,d2
	moveq	#$23,d3
	lea	label2(PC),a1
	lea	label3(PC),a2
	move.w	#(1024/8)-1,d7		; numero di loops = 128
LoopOK:

	rept	8		; replico 8 volte il pezzo...

	add.l	d2,(a1)
	sub.l	d3,(a2)
	move.l	(a1),(a0)+
	move.l	(a2),(a0)+
	add.l	d0,(a0)+
	sub.l	d1,(a0)+

	endr

	dbra	d7,LoopNormale
	rts

Comunque, per rendere velocemente tutto PC relative, c' un sistema.
Se in un registro indirizzi stabilito, ad esempio a5, mettiamo l'indirizzo
dell'inizio del programma, o comunque un indirizzo conosciuto nel nostro
programma, baster indicare la nostra label come a5+offset per trovare la
label in questione. Ma dovremmo fare questo "A MANO"????
Nooooo! Ecco un sistema molto veloce per fare questo:

S:				; Label di riferimento
MYPROGGY:
	LEA	$dff002,A6	; In a6 abbiamo il custom register
	LEA	S(PC),A5	; In a5 il registro per l'offset delle label

	MOVE.L	#$123,LABEL2-S(A5)	; label2-s = offset! Es: "$364(a5)"

	MOVE.L	LABEL2(PC),d0		; Qua agiamo normalmente

	MOVE.L	d0,LABEL3-S(A5)		; stesso discorso.

	move.l	#$400,$96-2(a6)		; Dmacon (in a6 c' $dff002!!!)

	...

; mettiamo di aver "sporcato" il registro A5... baster ricaricarlo!

	LEA	S(PC),A5
	move.l	$64(a1),OLDINT1-S(A5)
	CLR.L	LABEL1-S(A5)

Mi pare chiaro no? La label la potevate chiamare BAU: anzich S:, ma credo
che sia utile chiamarla S:, E:, I:, che  pi corto da scrivere.
L'unica limitazione  che se la label dista pi di 32K dalla label di
riferimento, andiamo fuori dei limiti di indirizzamento. Questo non  un
problema insormontabile, infatti basta mettere una label di riferimento
ogni 30K, e riferirsi alla pi vicina, esempio:

B:
	...
	LEA	B(PC),A5
	MOVE.L	D0,LABEL1-B(A5)
	...

; passano 30K

C:

	LEA	C(PC),A5
	MOVE.L	(a0),LABEL40-C(A5)
	...

Questo sistema inoltre rende difficile da disassemblare il vostro codice, nel
caso che qualcuno voglia "rubarvi" le vostre routine con un dissassemblatore.

Altra cosa che pu esservi utile  l'uso dei bit come flag. Per esempio,
se nel nostro programma abbiamo delle variabili che devono essere VERE o
FALSE, ossia ACCESE o SPENTE,  inutile sprecare un byte per ognuna di
esse. Baster un bit, e risparmieremo spazio. Per esempio:

Opzione1	=	0
VaiDestra	=	1	; Vai a Destra o a Sinistra?
Avvicinamento	=	2	; Avvicinamento o Allontanamento?
Music		=	3	; Musica Accesa o Spenta?
Candele		=	4	; Candele accese o spente?
FirePremuto	=	5	; qualcuno ha premuto fire?
Acqua		=	6	; il laghetto sotto?
Cavallette	=	7	; Ci sono le cavallette?

Controllo:
	move.w	MieiFlags(PC),d0
	btst.l	#Opzione1,d0
	...


CambiaFlags:
	lea	MieiFlags(PC),a0
	bclr.b	#Opzione1,(a0)
	...

MieiFlags:
	dc.b	0
	even

Comunque, se non amate i btst e i bclr/bset/bchg si pu fare cos:

	bset.l	#Opzione1,d0	->	or.b	#1<<Opzione1,d0

	bclr.l	#Opzione1,d0	->	and.b	#~(1<<Opzione1),d0

	bchg.l	#Opzione1,d0	->	eor.b	#1<<Opzione1,d0

Da notare l'utilit delle funzioni dell'asmone di shift ">>" e "<<", nonch
l'eor "~".

Per terminare la sezione sulle ottimizzazioni della CPU, faccio presenti
alcuni accorgimenti che velocizzano solo su 68020 e superiori, ma dato che
farli non costa niente, pu essere utile per veder schizzare maggiormente
le nostre routines su computer pi veloci.
Prima di tutto, ci sono le cache, che permettono di caricare loops lunghi
fino a 256 bytes, per cui dal secondo ciclo in avanti saranno letti dalla
memoria interna alla cpu!!!!!!!!!!!!!! E non dalla lenta memoria (specie
se chip-ram!). Di conseguenza,  bene ripetere le operazioni come abbiamo
visto, nei vari loop, in modo che siano grandi sui 100-150 bytes.
In questo modo, su 68020+ gireranno molto pi veloci di routines in cui
invece si sono messi in fila tante istruzioni quanti erano i loop da fare.
Per intendersi, se abbiamo:

Routine1:
	move.w	#2048-1,d7
loop1:
	< blocco di istruzioni >
	dbra	d7,loop1

Possiamo ottimizzarlo in:

Routine1:
	rept	2048
	< blocco di istruzioni >
	endr	

Che su un 68000 di base  molto pi veloce, ma su un 68020  pi lento!
Per fare un'ottimizzazione che sia veloce al massimo in tutti i casi:

Routine1:
	move.w	#(2048/16)-1,d7
loop1:
	rept	16
	< blocco di istruzioni >
	endr

	dbra	d7,loop1

Supponiamo che il blocco di istruzioni sia lungo 12 bytes, allora 12*16 fa
192, che sta nella cache, e va velocissimo su 68020, mentre su 68000 
impercettibile la differenza con la versione con 2048 di rept, e si risparmia
anche in lunghezza dell'eseguibile. Attenzione solo a non fare i loop lunghi
proprio 250 o 256 bytes, perch la cache pu essere riempita solo secondo
certe condizioni di "blocchi" e di "allineamento". Quindi state sempre sotto
i 180-200 bytes, per sicurezza.

Altra cosa da tener presente,  che se  possibile bisogna evitare di
accedere alla memoria consecutivamente. Ad esempio:

	move.l	d0,(a0)
	move.l	d1,(a1)
	move.l	d2,(a2)
	sub.l	d2,d0
	eor.l	d0,d1
	add.l	d1,d2

Andrebbe "riformulato" in:

	move.l	d0,(a0)
	sub.l	d2,d0
	move.l	d1,(a1)
	eor.l	d0,d1
	move.l	d2,(a2)
	add.l	d1,d2

Infatti, quando si accede alla memoria (specie se chip), ci sono i cosiddetti
WAIT STATE, ossia tempi d'attesa prima di poterci riscrivere. Nel primo
esempio, tra una scrittura e l'altra c' un tempo morto in cui il processore
attende che si possa riscrivere in ram. Nel secondo caso, invece, dopo aver
scritto in ram viene eseguita una operazione tra registri, interna alla cpu,
dopodich si accede nuovamente alla chip ram, a tempo d'accesso passato.
Se si accede a FAST RAM a 32bit il problema  molto meno forte, ma esiste.

Infine, i 68020+ gradiscono molto le routines e le label allineate ad
indirizzi multipli di 32, ossia allineate a longword.
Per allineare a 32 bit, baste un:

	CNOP	0,4

Prima della routine o della label. Su 68000 non ci sono miglioramenti, ma
ci sono su 68020+, specialmente se il codice allineato va in fast ram o in
cache. Ecco un esempio:

Routine1:
	bsr.s	rotazione
	bsr.s	proiezione
	bsr.s	disegno
	rts

	cnop	0,4
rotazione:
	...
	rts

	cnop	0,4
proiezione:
	...
	rts

	cnop	0,4
disegno:
	...
	rts

Per le label, vedete di non accedere ad indirizzi dispari, cosa che rallenta
molto, piuttosto, allineate anche queste a long:

Versione originale:

Label1:
	dc.b	0
Label2:
	dc.b	0	; indir. dispari! il "move.b xx,label1" sar lento!
Label3:
	dc.w	0
Label4:
	dc.w	0
Label5:
	dc.l	0
Label6:
	dc.l	0
Label7:
	dc.l	0

Versione allineata:

	cnop	0,4
Label1:
	dc.b	0
	cnop	0,4
Label2:
	dc.b	0
	cnop	0,4
Label3:
	dc.w	0
	cnop	0,4
Label4:
	dc.w	0
	cnop	0,4
Label5:
	dc.l	0
Label6:
	dc.l	0 ; queste 2 sono allineate sicuramente, non occorre cnop
Label7:
	dc.l	0

Per verificare se una label  allineata a 32 bit, assemblate, poi verificate
a che indirizzo si trova tale label col comando "M", poi dividete l'indirizzo
per 4, e moltiplicate di nuovo il risultato per 4.
Se torna l'indirizzo originario, significa che  un multiplo di 4, e tutto 
OK, se viene diverso significa che c' un resto e non  un multiplo di 4.
Allora mettete dei "dc.w 0" sopra l'indirizzo e provate ad allinearlo "a mano"
e mandate a quel paese l'assemblatore, che  un p suonato.
Comunque, se la vostre routine funziona gi al cinquantesimo, senza scatti
su un a500, risparmiatevi di mettere tutti quei "cnop 0,4" a incasinarvi il
listato. Cnoppate solo i listati con routines molto pesanti che non vanno
entro un frame, come routines frattali, o 3d "esagerati", eccetera.

******************************************************************************
*			OTTIMIZZAZIONI DEL BLITTER			     *
******************************************************************************

Alla fine faremo un altro esempio legato al Blitter.
Questo genere di ottimizzazioni che abbiamo trattato fino ad adesso erano
riferite solamente al 68000, quindi indipendenti dalla macchina a cui
si riferivano, ora tratteremo delle ottimizzazioni legate all'hardware
dell' Amiga, precisamente al Blitter.
Come ben sapete il blitter  un potenete coprocessore per lo spostamente di
dati ben pi veloce del 68000 di base (attenzione per che  pi lento
di un 68020+!).  bene trarre il massimo dal Blitter.
In genere una filosofia che si adotta per il blitter  che prima inizio
il trasferimento dei dati e prima finisco. Tuttavia dovete tenere sempre ben
presente il bit chiamato blit nasty che  in grado di dare maggiore priorit
al Blitter rispetto alla CPU, in pratica il bus per il trasferimento dei
dati sar per la maggior parte del tempo del Blitter, vediamo un esempio:

a6=$dff000
			; Supponiamo di aver inizializzato tutti i registri
	
	Move.w	d0,$58(a6)	; BLTSIZE - Il Blitter inizia
Wblit:
	Move.w	#$8400,$96(a6)	; Abilitiamo il blit nasty
Wblit1:
	Btst	#6,2(a6)	; Attendiamo che il blitter abbia finito
	Bne.s	Wblit1
	Move.w	#$400,$96(a6)	; Disabilitiamo il blit nasty
	....

Questo  un caso banale, perch mentre il blitter lavora la CPU potrebbe
fare qualche altra cosa, quindi quel ciclo di attesa  antiproduttivo.
Infatti, in computer con sola CHIP RAM questa funzione blocca del tutto
il processore, e forse non andrebbe mai usata.
Ma il caso in cui possiamo e dobbiamo abilitare il blit nasty  nei casi in
cui dobbiamo copiare a video un bitplane bob per bitplane, allora, poich
di solito la CPU deve attendere tra una blittata ed una altra, possiamo
tranquillamente abilitare il blit nasty. Vediamo un esempio:

BLITZ:				; I registri sono gi stati abilitati
	Move.w	#$8400,$96(a6)	; Abilitiamo il nasty
	Move.l	Plane0,$50(a6)	; Puntatore al canale A
	Move.l	a1,$54(a6)	; Puntatore al canale D
	Move.w	d0,$58(a6)	; Go Blitter!!!
WBL1:
	Btst	#6,2(a6)	; Qui la CPU deve attendere la fine...
	Bne.s	WBL1		; quindi il blitter deve andare al massimo!
	Move.l	Plane1,$50(a6)	; Puntatore al canale A
	Move.l	a2,$54(a6)	; Puntatore al canale D
	Move.w	d0,$58(a6)	; Go Blitter!!!
WBL2:
	Btst	#6,2(a6)	; Come sopra
	Bne.s	WBL2
	Move.l	Plane2,$50(a6)	; Idem
	Move.l	a3,$54(a6)
	Move.w	d0,$58(a6)
WBL3:
	Btst	#6,2(a6)
	Bne.s	WBL3
	Move.w	#$400,$96(a6)	; A questo punto il nasty pu anche essere
	Rts			; disabilitato.


Questo esempio mi d la possibilit di farvi notare una caratteristica
del blitter che  quella di non modificare alcuni valori dei suoi registri,
ad esempio nei registri di modulo(BltAMod,BltBMod,etc..). Troveremo
al termine della blittata gli stessi valori, quindi non c' bisogno
di inizializzarli se il modulo sar lo stesso per la prossima blittata.
La stessa cosa vale per i registri tipo BltCon0, BltCon1, BltFWM, BltLWM, ma
ci non  pi valido per i registri puntatori poich questi lavorano con
un indirizzamento con incremento.
Ci ci suggerisce la seguente cosa: supponiamo di avere un bob di 5 bitplane
da piazzare uno per uno in un bitplane "video", quindi ogni
volta carichiamo il puntatore al bitplane "video" nel registro D e il
puntatore al bob in A: dopo la prima blittata il il registro D sar
caricato con lo stesso valore pi una certa quantit per puntare al bitplane
successivo, ma lo stesso sar inutile farlo con il canale A, poich se il
nostro bob  stato memorizzato in memoria come bitplane successivi,
allora dopo la prima blittata il canale A punter automaticamente al
secondo bitplane del bob.
Possiamo ottenere validi risultati facendo anche nel seguente modo.
Riserviamo una zona di memoria con tutti i valori da passare ai registri
del blitter (nel nostro caso la zona parte da DataBlit).
Quindi in alcuni registri indirizzi carichiamo gli indirizzi dei registri
del blitter in modo tale da poterci accedere pi velocemente, e copiamo i
dati preconfezionati per far partire il blitter, accedendo direttemente ai
registri della CPU. Vediamo un esempio:

	Lea	$dff002,a6	; a6 = DMAConR
	Move.l	DataBlit(pc),a5	; quindi a5 punta a una tabella di valori
				; precalcolati

; Carichiamo ora i registri indirizzi

	Lea	$40-2(a6),a0	; a0 = BltCon0
	Lea	$62-2(a6),a1	; a1 = BltBMod
	Lea	$50-2(a6),a2	; a2 = BltApt
	Lea	$54-2(a6),a3	; a3 = BltDpt
	Lea	$58-2(a6),a4	; a4 = BltSize
	Moveq	#6,D0		; d0 costante per il controllo dello stato
				; del blitter.
	Move.w	(a5)+,D7	; Numero di blittate
	Move.w	#$8400,$96-2(a6) ; Abilitiamo il nasty
BLITLOOP:
	Btst	d0,(a6)		; Attendiamo come sempre la fine di qualche
	Bne.s	BLITLOOP	; operazione.
				; Prima di guardare qui sotto facciamo un
				; osservazione, se in a0 ho il valore $40000
				; ed eseguo le istruzione in tre casi distinti
				; a)Move.b #"1",(a0)
				; b)Move.w #"12",(a0)
				; c)Move.l #"1234",(a0)
				; otterr la seguente cosa:
				;         (a)	(b)	(c)
				; $40000	"1"	"1"	"1"
				; $40001	"0"	"2"	"2"
				; $40002	"0"	"0"	"3"
				; $40003	"0"	"0"	"4"
				; Ora faremo una cosa del genere...
	Move.l	(a5)+,(a0)	; $dff040-42 cio Bltcon0-Bltcon1
	Move.l	(a5)+,(a1)	; $dff062-64 cio BltBMod-BltAMod
	Move.l	(a5)+,(a2)	; $dff050 - Canale A
	Move.l	(a5)+,(a3)	; $dff054 - Canale D
	Move.l	(a5)+,(a4)	; $dff058 - BLTSIZE... START!!
	Dbra	d7,BLITLOOP	; Questo per d7 volte.


In questo esempio abbiamo adoperato varie tecniche di ottimizzazione, di
cui abbiamo gi parlato, in ogni caso vediamone qualcuna. 
Innanzitutto quando dobbiamo eseguire un gran numero di volte un ciclo ed
all'interno c' una operazione che coinvolge una costante (cio un dato
immediato), conviene mettere questo valore in un registro che non si user
nel ciclo, quindi effettuare l'operazione che coinvolge questo valore
direttamente col registro che lo contiene, evitando un accesso alla memoria.
Nel nostro caso abbiamo adoperato questa strategia caricando il valore del
bit da testare, per controllare se il blitter aveva terminato il suo compito,
nel registro d0.
In pratica abbiamo adottato una delle prime regole di cui vi ho parlato
all'inizio cio cercare sempre di tenere i valori nei registri.
Inoltre, abbiamo caricato $dff002 come base e non $dff000. Questo viene fatto
spesso, per eliminare il tempo usato nel waitblit a calcolare l'offset:

	Btst	#6,2(a6)	; a6 = $dff000

 pi lento di:

	btst	d0,(a6)		; a6 = $dff002, d0 = 6

Basta ricordarsi di mettere un -2 prima di (a6) per ottenere l'offset giusto:

	$54-2(a6)	; BltDpt
	$58-2(a6)	; BltSize
	$96-2(a6)	; DmaCon
	...

 importante che il waitblit sia veloce, in quanto prima si "accorge" che
la blittata  finita, prima si comincia quella dopo!
Per questo, evitate di chiamare il waitblit con un BSR, bens mettetelo
sempre sul posto, anche ripetendolo ogni volta che serve.

Lo stesso discorso che abbiamo fatto ora lo abbiamo applicato anche per i
registri del blitter caricandoli nei registri della CPU, evitanto accessi
alla memoria (in pratica alla memoria ci accediamo comunque per inizializzare
il blitter, ma evitiamo ogni volta di prelevare l'indirizzo dalla memoria)
Inoltre abbiamo usato un trucco che chiunque programmi giochi o demo adopera,
cio invece di tenere in memoria le dimensioni del bob per poi calcolarsi il
valore del bltsize, teniamo direttamente il valore del bltsize, questo lo
abbiamo fatto tramite la tabella DataBlit.
Tuttavia, come vi ho accennato sopra, mentre il blitter lavora il 68000 pu
fare qualche altra cosa, ad esempio se il blitter sta cancellando una zona
di memoria, il 68000 da buon cristiano pu dargli una mano, ad esempio:


	btst	#6,2(a6)
WaitBlit:
	btst	#6,2(a6)
	bne.s	WaitBlit
	Moveq	#-1,d0
	Move.l	d0,$44(a6)		; -1 = $ffffffff
	Move.l	#$9f00000,$40(a6)
	Moveq	#0,d1
	Move.l	d1,$64(a6)
	Move.l	a0,$50(a6)
	Move.l	a1,$54(a6)
	Move.w	#$4414,$58(a6)		; Il blitter inizia a pulire...
	Move.l	a7,OldSp
	Movem.l	CLREG(pc),d0-d7/a0-a6	; Puliamo i registri
	Move.l	Screen(pc),a7		; Indirizzo della zona da cancellare
	Add.w	#$a8c0,a7		; andiamo alla sua fine (+$a8c0)

	Rept		1024		; Il 68000 inizia a pulire
	Movem.l	d0-d7/a0-a6,-(a7)	; Pulisci 60 bytes per 1024 volte
	EndR

	Lea	$dff000,a6
	Movea.l	OLDSP(pc),a7
	Rts

CLREG:
	ds.l	15


Come vedete, qua il blitter e la cpu puliscono "in contemporanea" mezzo
schermo per uno. Naturalmente in questo caso il nasty bit non deve essere
settato, o la cpu non pu pulire in pace.

Il miglior metodo per aumentare le prestazioni del proprio programma
rimane per quello di migliorare i propri algoritmi, molto spesso.
Non pensiate ad esempio che implementare in assembly un pessimo algoritmo
di ordinamento, come il Buble Sort, sia pi veloce del miglior algoritmo di
ordinamento, come il Quick Sort, implementato in C.
Se il vostro algoritmo non vuole proprio girare pi velocemente anche dopo
aver adoperato le migliori tecniche di ottimizzazione, b, allora cancellatelo
e riscrivetelo completamente con un algoritmo migliore IN PARTENZA.
Ed anche se siete in possesso del miglior algoritmo, cercate sempre di
ottimizzarlo in modo da farlo girare su delle macchine anche non veloci,
non come nel mondo dei PC dove un programmatore di un 486 si sente soddisfatto
solo se il suo codice gira velocemente sulla propria configurazione.
Che ci vuole a fare routines veloci, se poi sulla confezione del gioco o del
programma si legge: CONFIGURAZIONE MINIMA: PENTIUM 60Mhz con 8MB di RAM.

