In un paio di articoli precedenti abbiamo trattato il tema della “classificazione”, rispettivamente, di un sistema e di una CPU (o processore in generale) in termini di “bit”.
Tolti di mezzo i meccanismi psicologici di appagamento & tutela del prodotto acquistato da parte di un consumatore e quelli che portano i markettari a sparare quanto più in alto possibile, appare in ogni caso evidente che, dall’invenzione dei primi calcolatori elettronici in poi, abbiamo assistito a un generale aumento delle capacità dei processori di manipolare informazioni costituite da sempre “più bit”.
Non è un caso se oggi ci troviamo immersi nell’era dei sistemi a 64 bit (che fra qualche anno diventeranno i soli a esser venduti) quando fino a qualche anno fa gli unici presenti erano quelli a 32 bit, e i 64 bit appartenevano alla fantasia e a server di fascia alta fino ad arrivare ai supercomputer.
Ma siamo sicuri che servano realmente? E, soprattutto, siamo sicuri che poter manipolare dati “con più bit” migliori le prestazioni?
Chi è passato in mezzo alla “guerra dei Mhz” combattuta da Apple prima coi suoi PowerPC e da AMD poi con gli Athlon, sempre nei confronti dell’onnipresente Intel, sa già che “più Mhz” non implica necessariamente più “velocità”, e a buon ragione lo dimostrano i risultati dell’esecuzione delle applicazioni real world e… la fine dell’architettura NetBurst.
La questione non è così semplice e ci sarebbe da spiegare e ricordare un po’ di cose, ma il tema su cui mi voglio soffermare oggi è quello dei “bit”, non dei “Mhz” (e poi in un passato non ancora remoto se n’è parlato in abbondanza), per i quali le considerazioni sono comunque le stesse, come avremo modo di appurare.
Prima di tutto è necessario definire bene il significato di informazione “a n bit”, visto che ne esistono sostanzialmente di due tipi all’interno di un processore: i dati e gli indirizzi, e il numero di bit attribuibili a ciascuno possono benissimo essere diversi. Ad esempio il 6502 poteva manipolare dati a 8 bit, ma aveva indirizzi a 16 bit.
Questa distinzione è importante perché il primo tipo di informazione riguarda le capacità di calcolo vere e proprie (sto volutamente semplificando il discorso, senza citare unità SIMD et similia), mentre il secondo è associato alla capacità di indirizzamento della memoria.
Ecco perché un microprocessore a 8 bit poteva eseguire calcoli con interi da 0 a 255 (o da -128 a 127, se consideriamo gli 8 bit con segno), ma aveva la necessità di poter indirizzare più di 256 locazioni di memoria: erano veramente troppo poche per realizzare qualcosa di utile.
Come ci si poteva intuitivamente aspettare, aumentando il numero di bit per manipolare informazioni aumentano anche i problemi. Ad esempio, gestire puntatori a 16 bit nel 6502 aveva un costo maggiore rispetto alla manipolazione dei dati a 8 bit, e questo perché:
- l’interfaccia verso la memoria era a 8 bit, per cui prelevare o memorizzare quantità a 16 bit comportava necessariamente due accessi a essa;
- l’ALU a 8 bit veniva usata più volte per eseguire operazioni a 16 bit.
Tutto ciò si riflette in un maggior utilizzo di transistor (e, quindi, anche di costi di progettazione e produzione), consumo di corrente, dissipazione di calore, spazio, e possono avere ripercursioni anche sulle prestazioni della CPU. Si tratta, in ogni caso, di un “male” necessario (mentre alla fine parlerò di un caso del tutto diverso), per quanto detto prima.
Considerato che l’articolo è incentrato sulle prestazioni legate all’aumento del numero dei bit, potremmo pensare a delle soluzioni per cercare di eliminare o comunque migliorare questo problema per il 6502. Le medesime considerazioni saranno applicabili anche ad altri microprocessori, con i dovuti “aggiustamenti” (è il concetto che m’interessa esporre).
Per quanto riguarda i calcoli a 16 bit, la soluzione è molto semplice: basta realizzare un’ALU in grado di eseguire operazioni a 8 e 16 bit in un solo ciclo di clock. Cosa abbastanza comune per le CPU a 16 bit, ma che richiede molti più transistor e una modifica dell’architettura interna del 6502 (il cui bus dati interno era esclusivamente a 8 bit, come potete vedere dalla pagina 2 del datasheet).
Anche per l’accesso in memoria a 16 bit la soluzione sembra banale e a portata di mano: sarebbe sufficiente realizzare un bus dati (interno ed esterno) a 16 bit, com’è stato fatto per il 68000, ad esempio, al costo di un’interfaccia esterna più complicata (dev’essere possibile leggere e scrivere dati indifferentemente a 8 bit o 16 bit).
In realtà si tratta di una soluzione parziale, poiché rimane valida esclusivamente per letture o scritture di dati a 16 bit che siano allineati a questa quantità di bit. Questo significa che va benissimo l’accesso a un indirizzo pari (ad esempio $C0DE), ma non per uno dispari (ad esempio $0BAD).
Infatti in quest’ultimo caso il byte meno significativo (il 6502 è un processore little-endian) del dato a 16 bit a cui si vuole accedere si trova nel byte alto della locazione a 16 bit $0BAC, mentre quello più significativo in quello basso della successiva $0BAE (sempre a 16 bit).
Statisticamente parlando, però, abbiamo ottenuto mezza soluzione al problema. Il 68000 lo risolve definitivamente e in maniera elegante (si fa per dire, visto che è assolutamente dispotico): non permette l’accesso di dati a 16 bit a indirizzi dispari, pena il sollevamento di un’eccezione address error.
Soluzione applicabile anche al 6502, ma imponendo però limitazioni alla sua ISA. In primo luogo obbligando i programmatori a utilizzare esclusivamente locazioni pari nelle modalità d’indirizzamento indiretto (le uniche che consentono di prelevare quantità a 16 bit in maniera programmatica), e forzando lo stack a indirizzi sempre a 16 bit memorizzandovi esclusivamente valori di questa dimensione.
Ma se per i programmatori la prima parte è abbordabile (si tratta di organizzare in maniera opportuna le 256 locazioni di memoria utilizzabili allo scopo), forzare lo stack a indirizzi e dati sempre a 16 bit porrebbe troppe limitazioni.
Infatti lo stack è costituito da soli 256 byte, e in questo modo consentirebbe di memorizzare soltanto 128 valori, anche se la stragrande maggioranza dello scambio di dati avviene per quantità a 8 bit (non a caso il 6502 è una CPU a 8 bit). Portare la dimensione dello stack a 512 byte sarebbe un’altra soluzione, ma si sprecherebbe troppo spazio.
Situazioni del genere esistono per tanti altri microprocessori. Ad esempio il 68000 ha all’incirca lo stesso problema del 6502: è un microprocessore a 32 bit, ma con un bus dati esterno a 16 bit, per cui accedere a dati o indirizzi a 32 bit comporta necessariamente due accessi alla memoria.
Altri processori presentano problemi con l’allineamento dei dati, similmente al 68000. Infatti, pur disponendo di bus dati in grado di leggere o scrivere dati e/o indirizzi della stessa dimensione dei rispettivi registri interni, sono in grado di farlo esclusivamente per indirizzi allineati alla medesima quantità. E’ una scelta comune a molti RISC, come ARM e PowerPC (giusto per citare due fra i più comuni rappresentanti della categoria).
Altre CPU, come gli 80×86, non impongono limitazioni di questo tipo (è possibile comunque forzarne il funzionamento in modo da obbligare a effettuare accessi allineati), ma in questo caso le prestazioni ne risentiranno in presenza di accesso a indirizzi disallineati (rispetto alla dimensione del dato), e per questo motivo in tutti i manuali riguardanti l’ottimizzazione per questa ISA è consigliato allineare opportunamente i dati.
Rimane in piedi la domanda posta a inizio articolo: più bit equivale a migliori prestazioni? Non sempre: dipende tutto dai problemi che devono essere risolti dalle applicazioni (e sarebbe opportuno scegliere sempre dimensione dei dati adeguate a ciò che realmente serve). Anzi, a seconda dell’ISA, più bit può equivalere a un decadimento della velocità di esecuzione.
Sembra strano, se consideriamo che il passaggio dai 32 bit ai 64 bit per l’architettura x86 ha comportato mediamente un miglioramento del 10-15% dei tempi di calcolo, e il motivo per cui ciò non si ripete con altre architetture è presto detto: con x86-64 AMD non ha semplicemente portato la dimensione dei registri da 32 a 64 bit, ma ha apportato all’ISA notevoli altre migliorie che hanno ammortizzato (e mediamente anche superato) la penalizzazione dovuta a tale passaggio.
Infatti il solo “allargamento” dei registri comporta sempre un peggioramento delle prestazioni, dovuto a un aumento dei tempi di context switch, a un maggior consumo di spazio, di banda in lettura / scrittura verso la memoria ed eventuali cache causata dalla dimensione doppia dei puntatori (tipologia di dati fortemente utilizzata nelle applicazioni), e di page fault e TLB miss (entrambi a causa del maggior spazio richiesto).
Ecco perché utilizzare un G5 di IBM in modalità a 32 o a 64 bit comporta in quest’ultimo caso un decadimento delle prestazioni, se non si fa specifico utilizzo delle capacità di elaborazione a 64 bit e/o non si utilizzano più di 4GB di memoria, come si può notare dalle due pagine dedicate a Tiger e alla scrittura delle applicazioni a 64 bit.
Tiger, infatti, è stata la prima versione di OS X in grado di supportare applicazioni a 64 bit per i G5, ma Apple ne ha fortemente sconsigliato l’utilizzo, se non per particolari e ben precisi ambiti applicativi (estremamente limitati e contorti, se consideriamo che, ad esempio, non era possibile realizzare applicazioni interamente a 64 bit; cosa che sarà possibile soltanto con Snow Leopard, che è alle porte), proprio per le negative ricadute sul fronte prestazionale.
Come per il 6502, anche per il G5 (e altri microprocessori che hanno avuto all’incirca gli stessi problemi, come i MIPS e gli SPARC, ad esempio), si possono prevedere delle soluzioni adeguate quanto meno per contenere l’impatto dell’aumentata dimensione dei registri, ma aumentare la dimensione del bus dati, delle cache, il numero delle entry nella cache TLB, ecc. non è cosa semplice e, soprattutto, può essere particolarmente dispendioso.
Si tratta, in definitiva, di trovare sempre il giusto compromesso, una soluzione che cerchi di bilanciare tutte le variabili in gioco. Appare, quindi, particolarmente strana la decisione di IBM/Sony/Toshiba da un lato e di IBM/Microsoft dall’altro, di dotare la PlayStation 3 e la XBox 360 di CPU a 64 bit (e qui mi riallaccio a quanto detto sopra sul male “necessario”).
Si tratta di console dotate di 512MB di RAM (29 bit d’indirizzamento necessari), e per le quali mi sembra alquanto difficile ipotizzare che abbiano avuto un “massivo” bisogno di manipolare dati a 64 bit. Dunque perché non sviluppare Cell e Xenon sulla base dell’ISA a 32 bit dei PowerPC, eliminando gli svantaggi di cui parlavo prima?
Un altro mistero che si va ad aggiungere alla storia dell’informatica…