Visualizza messaggio singolo
Vecchio 28-11-2007, 18.32.31   #3 (permalink)
MarcoGT
VV.com Aficionados
 
L'avatar di MarcoGT
 
Data registrazione: 27-11-2007
Residenza: near COD NDB
Messaggi: 687
MarcoGT is on a distinguished road
Predefinito

...continua...

1) Sulla base di una analisi statistica dei programmi, si scopre che per il 90% del tempo il processore utilizza sempre un ristretto sottinsieme di istruzioni.

2) Perchè allora non ottimizzare il processore nell'esecuzione diretta di queste poche istruzioni lasciando al compilatore l'onere di spezzettare le istruzioni più rare e molto più complesse in task più semplici? In tal modo torna in auge il ruolo del compilatore e si può fare a meno della ROM di decodifica.

3) Non solo: se il processore è in grado di eseguire direttamente in modo ottimizzato poche ma importanti istruzioni, facciamo in modo che ogni istruzione venga completata in un solo ciclo di clock!! (macchina a singolo ciclo)

4) Inoltre, l'esecuzione dei programmi è spesso rallentata dai ripetuti accessi in memoria centrale ordinati dalle varie istruzioni con indirizzamento complesso: decidiamo allora di fare tabula rasa di queste istruzioni e stabiliamo che l'accesso in memoria avvenga esclusivamente tramite due comandi: il load per il caricamento del dato dalla memoria al registro e lo store per la scrittura dal registro alla memoria.

5) Visto che gli accessi in memoria centrale adevono essere limitati il più possibile, occorre disporre sul chip di un consistente numero di registri per avere un magazzino di informazione sufficientemente capiente da consentire l'elaborazione dei dati . Questo insieme di registri è visibile al programmatore in assembler che in tal modo produce un codice altamente ottimizzato per la macchina che deve eseguire il programma.

Per capire meglio i punti 4) e 5) facciamo un esempio. Prendiamo questo pezzo di codice:

j=0;
for (i=0; i<100 ; i++)
j = j + i;

è scritto in C (o anche J A V A) e dice grosso modo questo: ad ogni ciclo, aggiungi il valore corrente di i a j e salva il risultato in j; fatto questo, incrementa i di una unità e se raggiunge il valore 100 esci dal ciclo. Se il compilatore non è "scemo", è evidente che salva i e j in due registri locali e quindi effettua su di essi le operazioni; solo a ciclo concluso salverebbe i risultati in memoria. Un processore CISC, però, il cui codice assembler è molto poco ottimizzato, potrebbe benissimo prevedere invece una infinità di accessi e di scritture in memoria centrale per prelevare e aggiornare i valori di i e j! (questo fa già intuire come il compilatore in ultima analisi decide la bontà dei benchmark su una macchina anzichè su un'altra. Tutto questo causerebbe un rallentamento terribile delle prestazioni del sistema. Sorpresi? Beh, prima di fare un riassunto su quanto esposto torniamo al "formulone" sul calcolo della performance e facciamo qualche commento:

time/program = (instructions/program) x (cycles/instruction) x (time/cycle)

(instructions/program): un processore CISC tenta, come abbiamo visto, di diminuire questa quantità. Un RISC, invece, accetta un peggioramento di questo fattore.
(cycles/instruction): il processore RISC tende a portare questa quantità al valore unitario ossia un istruzione eseguita per ogni ciclo di clock!
(time/cycle): il RISC tende a far diminuire anche questa quantità, che è in sostanza legata al cammino critico discusso prima. Più breve è il critical path, maggiore è la frequenza di clock sopportabile dal processore. Nasce il concetto di pipeline: sfruttando la semplicità delle istruzioni RISC è possibile fare in modo che i vari passi di cui esse sono composte vengano eseguiti in cascata su più istruzioni sequenziali come in una catena di montaggio. Il risultato è la possibilità di eseguire le istruzioni in un solo ciclo di clock che è stata a lungo caratteristica unica dei processori RISC. Inoltre i passi della pipeline possono essere semplici e dotato di critical path basso con conseguenti elevate velocità di clock.

Dopo quanto detto è abbastanza evidente come nello scontro CISC vs RISC sia stata quest'ultima filosofia ad aver avuto la meglio. Di processori RISC oggigiorno ne abbiamo piene le letteralmente le tasche: dai palmari ai cellulari della prossima generazione, oggi ogni processore che voglia essere snello e al contempo potente nasce sotto l'effige del RISC. Gli stessi processori x86, come abbiamo visto hanno assimilato il paradigma RISC unica e vincente mossa che ne ha prorogato la vita oltre ogni rosea aspettativa. Del resto il percorso di sviluppo delle capacità di un processore non si ferma a quello che abbiamo detto.

L'obiettivo di qualsiasi progettista di processori è quello di ottenere sempre il max throughput complessivo, cioè, il massimo volume di dati elaborati e consegnati in uscita nell'unità di tempo. Come conseguire il massimo rendimento? La parola d'ordine in questi casi è parallelismo. Il parallelismo è stato il passo successivo compiuto dalla tecnologia dei processori dopo l'affermazione del RISC. Questo si esplica principalmente in due modalità differenti all'interno di un singolo processore: il pipelining e il superscaling.

Un'altra tecnica di ottimizzazione delle risorse è il multithreading, che come scuola di pensiero ha pochi anni alle spalle ma già promette interessanti innovazioni.


La generazione attuale dei microprocessori ha prestazioni, rispetto ai predecessori, superiori di svariati ordini di grandezza.
Anche se il progetto attuale di uan CPU varia enormemente da caso a caso, possono essere individuate alcune linee guida comuni ad ogni progetto; infatti ogni CPU effettua una generazione degli indirizzi, continene unità logico-aritmetiche (ALU), possiede file di registri ed ha un'interfaccia di sistema. Molti (direi tutti ormai) hanno una o più cache on-chip, un TLB (Translation Lookaside Buffer) e tutte le architetture attuali possideono un'unità floating point on-chip.

Il progettista, anzi, è meglio dire i progettisti di una CPU, devono affrontare una miriade di problemi.

Qui di seguito elencherò i problemi delle CPU moderne:

1) Le latenze della memoria e della cache di secondo livello

I primi microprocessori effettuavano il fetch (prelevamento) dell'istruzione direttamente dalla memoria; in questo modo dopo aver inviato una richiesta dati, la CPU doveva attendere un tempo "molto lungo" prima che i dati arrivassero, impedendo così alla CPU di operare alla velocità per cui era stata progettata.

L'implementazione della cache secondaria off-chip ha aiutato a migliorare questo inconveniente. Una memoria cache è solitamente di dimensioni limitate (dai 256kb su processori di fascia bassa al 1Mb su processori di fascia alta) e contiene un blocco di indirizzi di memoria comprendenti una piccola sezione della memoria principale. La memoria cache fornisce un accesso più rapido e può inviare i dati alla CPU ad una frequenza maggiore della memoria principale.

I sistemi di memoria con cache on-chip possono ulteriormente migliorare il problema, poiché permettono il completamento di un accesso in un singolo ciclo di clock. L'aumento di prestazioni fornito dalla cache di primo livello ha indotto i progettisti ad aumentare sempre di più le dimensioni causando in molte architetture un notevole aumento dello spazio dedicato al progetto della cache; infatti in molte implementazioni la cache occupa più dell'80% della superficie del die.
Le performance raggiungono il valore massimo quando l’applicazione può essere eseguita totalmente dentro la cache. Tuttavia, quando l’applicazione, come spesso avviene, è troppo grande per stare nella cache, le performance diminuiscono in modo significativo.
La cache di primo livello contiene un range di indirizzi che comprende un sottoinsieme di quelli presenti nella cache secondaria, che a loro volta sono un sottoinsieme degli indirizzi presenti nella memoria principale.


MarcoGT non è connesso   Rispondi citando