Schede di approfondimento:
Architetture evolute
Le tecniche utilizzate per migliorare le prestazioni dei sistemi di calcolo, partono dal semplice
aumento della frequenza del clock ed attraverso l'introduzione delle cache, della pipeline, della
tecnologia RISC, giungono fino alle ultime tecniche di integrazione tra RISC e CISC.
Aumento della velocità
Velocità dei procesori Intel in Milioni di Istruzione
Per Secondo
Gordon Moore ha stabilito nel 1975 una legge, detta legge di Moore, la quale recita che il numero
di transistor su ogni singolo integrato raddoppia ogni 18 mesi.
Da questa affermazione deriva che la potenza di calcolo raddoppia ogni 18 mesi.
Dall'introduzione del primo microprocessore si può rilevare un continuo aumento della velocità, che
va in due direzioni non antitetiche.
Da una parte vediamo che è aumentata la dimensione dei dati trattati con un'unica istruzione,
partendo dai quattro bit delle prime CPU (vedi INTEL 4004) fino ai trentadue bit delle
architetture correnti oggi fino ai sessantaquattro bit delle architetture oggi innovative.
Questo aumento del numero di bit si traduce in un aumento di velocità in quanto da una parte si è in
grado di trattare dati più complessi con singole istruzioni e dall'altra si è in grado di leggere più dati
con un'unica operazione di lettura.
L'altro tipo di aumento di velocità si è ottenuto incrementando semplicemente la velocità del clock
delle CPU, vale a dire riducendo il tempo per eseguire un'istruzione a parità di complessità
dell'istruzione stessa (a parità di cicli).
Anche in questo caso possiamo vedere un aumento della velocità del clock da 108 KHz del 4004
nel 1971 fino ai 300/400 MHz delle CPU dei giorni nostri, con un aumento del 38% circa ogni
anno, anche se l'aumento non è stato affatto costante.
Un altro filone per l'aumento della velocità delle CPU consiste nel cercare di ridurre il numero di
cicli di clock necessari per ogni singola istruzione.
Naturalmente questo numero dipende in primo luogo dalla complessità delle istruzioni stesse,
quindi se una CPU introduce un'istruzione molto potente ma anche complessa, questa richiederà
molti cicli per essere eseguita.
D'altronde, aumentando la complessità dei circuiti interni si è cercato di ridurre il numero di cicli
necessari per le istruzioni semplici.
Evoluzione1 delle Architetture a Microprocessore
•
Se le CPU hanno aumentato la loro velocità di 1000 volte, le memorie solo di 3
•
Inizialmente la memoria era più veloce del processore
•
Quando il processore ha superato la velocità della memoria, si sono introdotte le code di
prefetch
o
•
Le code possono compensare piccole differenze di velocità, perché quando il
processore "esegue" non usa la memoria
Per differenze più grandi si deve ricorrere alle memorie cache
Memoria cache
1
•
Una memoria cache è una piccola2 memoria veloce, che si interpone nel percorso dei dati
tra la RAM (DRAM, lenta) e la CPU
•
La memoria cache è una RAM (SRAM) veloce quanto la CPU (o poco meno)
•
Nella memoria cache vengono immagazzinati i dati (o istruzioni) usati più di recente
•
Quando la CPU richiede un dato, se esso è contenuto nella cache, viene restituito
rapidamente, se non c'è, occorre aspettare il tempo della RAM
http://www.itis.mn.it/inform/evarchi/indexb.htm con approfondimento http://www.itis.mn.it/inform/evarchi/index.htm
Memoria di limitata capacità (es. 64KB) in molti casi presente internamente al microprocessore stesso. La prima
volta che il microprocessore carica dei dati dalla memoria centrale, tali dati vengono caricati anche sulla cache.
Successivamente, i dati vengono letti dalla cache invece che dalla memoria centrale più lenta.
Sfrutta le caratteristiche di località spaziale e temporale tipica dei programmi.
Problemi di coerenza dei dati contenuti nella cache.
2
•
Nella cache, oltre ai dati vanno anche immagazzinate altre informazioni (dette TAG),
realtive all'indirizzo originale dei dati contenuti
•
I TAG sono costituiti da celle di memoria che memorizzano gli indirizzi delle celle dati
loro associate
•
Ogni cella dei TAG contiene, oltre alla memoria, anche un comparatore che confronta
l'indirizzo memorizzato con quello prodotto dalla CPU.
•
Se i due indirizzi coincidono (accadrà al massimo per una calla) si genera un HIT e la cache
fornisce il dato
•
Se l'indirizzo della CPU non corrisponde con nessun TAG, si genera un MISS ed il dato
deve essere prelevato dalla memoria
•
Una memoria come quella dei TAG, con i comparatori associati viene detta memoria
associativa.
•
Se vogliamo che ogni locazione (o ogni "word") di RAM possa andare in ogni cella di
cache, ogni cella di TAG deve avere la dimensione dell'address bus o poco meno.
•
Se ogni locazione di RAM può andare in ogni cella di cache, dobbiamo aggiungere un
meccanismo per scegliere la cella di cache da usare
•
Se vogliamo scartare il dato usato meno di recente, dobbiamo aggiungere una memoria
delle età, associata ad ogni cella, che ne indichi la più vecchia.
•
Una cache organizzata così prende il nome di fully associative
•
Una cache fully associative ha una quantità di bit di servizio paragonabile con la quantità di
bit di dati
•
Se in un certo periodo utilizziamo anche poche celle in più della capacità della cache,
ripetutamente, la cache viene ricaricata completamente ad ogni ciclo.
•
Se decidiamo che la cache possa immagazzinare un blocco di memoria, come TAG basta il
numero del blocco tra i blocchi che costituiscono la memoria
•
Per rendere più flessibile la cache, possiamo associare questo TAG non all'intera memoria
ma ad ogni singola cella
•
Una cache così organizzata prende il nome di direct mapping
•
Non servono più le memorie delle età, in quanto ogni cella di RAM può andare in una sola
cella di cache
•
Il numero dei bit aggiunti al dato cala considerevolmente
•
Se i dati che usiamo eccedono la dimensione della cache, ad ogni ciclo verranno ricaricati
solo i dati eccedenti
•
Se usiamo programmi con subroutine o dati sparsi, è facile che due dati debbano andare
nella stessa cella ad ogni ciclo, venendo quindi riletti dalla RAM
•
Per risolvere il problema dei dati sparsi o delle subroutine possiamo spezzare la cache in più
parti, messe in parallelo
•
In questo modo, più dati che "competono" per la stessa cella di cache, possono andare in
"fogli" diversi della cache
•
Per scegliere il "foglio" servirà una memoria delle età, ma molto piccola, proporzionata al
numero di "fogli"
•
La diminuzione della dimensione del "foglio" comporta anche un leggero aumento della
dimensione dei TAG
•
Una cache di questo tipo prende il nome di set associative
•
È il miglior compromesso tra fully associative e direct mapping
•
Per il momento si è ragionato sulla lettura. Se prendiamo in considerazione la scrittura si
pongono due casi:
o
Si può non usare la cache per la scrittura (write through), ma aggiornarla soltanto:
la scrittura sarà lenta quanto la RAM
o
Si può scrivere nella cache (write back):
la scrittura sarà veloce come la cache, ma si dovrà riscrivere il dato modificato in
RAM prima di scartarlo dalla cache.
•
Nel caso di architetture multiprocessore dovremo anche preoccuparci che i dati in cache
non siano resi obsoleti da scritture in RAM da parte di altri processori.
•
Per fare questo dovremo utilizzare dei protocolli che controllino l'attività del bus
•
Questi protocolli prendono il nome di protocolli di coerenza
Introduzione del parallelismo (pipeline)
•
Per aumentare la velocità della CPU possiamo spezzare l'esecuzione delle istruzioni in fasi
indipendenti da eseguire in parallelo
•
Le fasi (eseguibili già in parallelo) tradizionali sono fetch ed execute
•
Possiamo ulteriormente spezzare la fase di fetch in
o Fetch (lettura dell'opcode)
o Decode (interpretazione dell'opcode)
•
Possiamo ulteriormente spezzare la fase di execute in
o
o
o
Read (lettura degli operandi)
Execute (esecuzione dell'operazione)
Writeback (scrittura del risultato)
•
La struttura così ottenuta si chiama pipeline
•
Ammettendo che ogni stadio impieghi un ciclo per eseguire il suo compito, la pipeline è in
grado di "produrre" un'istruzione al ciclo
•
Esistono alcune situazioni che rallentano la pipeline:
1. Un'istruzione richiede il risultato della precedente
2. Uno stadio richiede più cicli per eseguire una determinata istruzione
3. Un salto costringe la pipeline a scartare le istruzioni "semilavorate" che non sono più
da eseguire
•
Quando si verificano queste situazioni, nella pipeline si creano bolle (manca l'istruzione da
eseguire) o stalli (non si può scaricare l'istruzione nello stadio successivo)
•
Solo il primo problema può essere risolto in maniera trasparente al programmatore
prendendo il dato appena è pronto (dall'uscita della execute) tramite la tecnica del feed
forward
Tecnologia RISC
•
Per ottenere il massimo della velocità occorre modificare il set di istruzioni
1. Occorre rendere le istruzioni tutte della stessa velocità, eliminando quelle complesse
che richiedono più tempo
2. Occorre modificare l'istruzione di salto, per eliminare le istruzioni scartate
•
Per il primo punto, si riduce il numero di istruzioni disponibili per il programmatore,
lasciando solo quelle semplici, senza indirizzamenti complessi (da qui il nome RISC Reduced Instruction Set Computer)
•
Per il secondo problema si introduce il delayed jump (salto dilazionato), vale a dire, un
salto che comunque esegue l'istruzione che lo segue
•
Per ridurre gli accessi alla memoria si introduce un numero elevato di registri
•
La semplificazione di ogni blocco ne consente l'aumento della velocità
La seguente tabella3 riassume le differenze più marcate tra i processori CISC e quelli RISC:
Numero di istruzioni
Modi di indirizzamento
Formato delle istruzioni
Ciclo medio per istruzione
Istruzioni che accedono alla memoria
RISC
CISC
< 100
1-3
1-2
≅1
solo
load/store
≥ 32
HW
≅ 10%
> 200
5-20
≥3
3-10
molte istruzioni
Registri
2-16
Unità di controllo
microcodice
Area destinata alla decodifica delle
> 50%
istruzioni
Naturalmente il confine tra sistemi CISC e RISC non è ben netto se ci si limita solo a considerare il
repertorio delle istruzioni, ma, come mostra la tabella, è la presenza di differenze che derivano dal
ricorso a metodologie di progetto specifiche che consente di definire un processore RISC o CISC.
Architetture Superscalar
3
4
•
Per aumentare ulteriormente la velocità si possono mettere in parallelo più pipeline.
•
Questa architettura prende il nome di superscalar4
•
Per alimentare le pipeline occorrerà dimensionare generosamente le cache e aumentare la
dimensione del data bus
•
La tecnica del Feed Forward diventerà molto più complessa e non sarà sempre sufficiente a
risolvere il problema
http://csc.unisi.it/e2c/dispense/f_in/cap3.pdf
Nel Pentium l’architettura superscalare è realizzata con la duplicazione dell’hardware
•
Il dealyed jump non riesce a risolvere il problema dei salti, in quanto cambia il numero di
istruzioni "pendenti".
•
Aumentando il numero di pipeline: in genere non tutte riescono ad eseguire tutte le
istruzioni
•
Per questo motivo le istruzioni non saranno sempre libere di percorrere qualunque pipeline e
dovranno a volte passare da una all'altra
•
In questo scenario a volte le fasi delle istruzioni o le istruzioni stesse potranno essere
eseguite in ordine diverso da come sono nel programma (esecuzione non in ordine)
•
Per risolvere il problema dei salti si introducono tecniche statistiche di previsione del loro
esito, attraverso le jump prediction table
Superamento del RISC
•
Le architetture RISC sono incompatibili con i vecchi processori (68000, 8086)
•
Se si vogliono far funzionare i nuovi processori con i vecchi set di istruzioni si devono
applicare delle tecniche particolari
•
Gli scogli principali da superare sono:


Istruzioni complesse
Numero limitato di registri
•
Le istruzioni complesse sono sempre scomponibili in sequenze di istruzioni semplici
•
Potremo quindi costruire un processore superscalar in grado di eseguire solo le istruzioni
semplici in cui le complesse sono scomposte (che appartengono ad un set RISC)
•
Potremo creare un "compilatore hardware" che interpreti le istruzioni complesse
traducendole in sequenze di istruzioni semplici
•
Il compilatore hardware alimenterà la cpu superscalar con le istruzioni così ottenute
•
Non è possibile aumentare i registri che il programmatore conosce (dovremmo modificare il
set di istruzioni)
•
Avendo pochi registri, spesso una relazione di dipendenza deriva dal fatto che serve un
registro ma sono tutti occupati (serve una risorsa)
•
Avendo dei registri in eccesso, potremmo creare una seconda copia del registro indicato (la
risorsa), e quindi far partire subito la seconda sequenza di operazioni.
•
Per fare questo serve una tabella che dica a che registro fisico corrisponde un certo registro
logico, per ogni istruzione
•
Questa tecnica prende il nome di register aliasing
Gradi di parallelismo
E’ possibile individuare i livelli architetturali nei quali è applicabile il concetto di parallelismo che
in ultima istanza deve consentire al sistema un significativo aumento nelle prestazioni.
I livelli possono essere distinti a partire dal basso della struttura elettronica e quindi:
L0: parallelismo su microistruzioni
L1: parallelismo su istruzione
L2: parallelismo sul sistema
L3: parallelismo sul processo
L4: parallelismo interno al programma
Evidentemente il parallelismo dei primi tre livelli implica un’evoluzione dell’hardware che non si
riflette, almeno in un primo momento, sulla natura del sw che dovrà girarvi sopra.
A livello di microistruzioni, a loro volta inserite in microprogrammi associati ad ogni istruzione e
utilizzate per espletare una qualsiasi delle fasi di processo di CPU (fetch, decode, operand fetch,
execute, operand store), si realizza un grado alto di parallelismo data la scomponibilità di molte
operazioni (es. Unità di Floating Point in parallelo, operazioni su Program Counter e di calcolo).
La tecnologia RISC (Reduced Instruction Set Computer), implementata parzialmente anche su
macchine CISC (Complex Instruction Set Computer, ad es. sullo 80486) contribuisce
all’ottimizzazione del codice a livello di microprogramma, riducendone e uniformandone i formati
e gli aspetti operativi.
A livello di istruzione è evidente come le tecniche di prefetch e di pipeline hanno contribuito a
parallelizzare il processo globale in CPU. Le questioni relative a modifiche del program counter ad
esempio sono state affrontate predisponendo particolari dispositivi interni alla CPU in grado di
predire statisticamente la modifica del Program Counter tramite opportune tabelle storiche dei salti
(Branch Table ad esempio adottata sul PENTIUM); in questo caso le code di prefetch possono
essere ricostruite in tempo reale e rese disponibili anche in caso di salto. La questione della
dipendenza dei dati è stata affrontata predisponendo opportuni buffer interni che consentono la
gestione intelligente del data-forwarding, potendo rilevare tempestivamente la dipendenza dei dati
tra istruzioni concorrenti e quindi operare salvataggi temporanei che consentono l’avanzamento
coerente del processo. Infine la questione dell’accesso concorrente a risorse comuni è stato
ampiamente affrontato duplicando opportunamente gli accessi ad esempio al bus di sistema,
predisponendo anche in questo caso buffer temporanei di memorizzazione.
Il parallelismo di sistema invece fu brillantemente affrontato ad esempio nel caso di trasferimenti
pesanti di I/O verso e per la memoria, trasferimenti che potevano essere eseguiti da dispositivi
autonomi che si appropriano del bus di sistema consentendo alla CPU di avanzare in parallelo ad
esempio in fase di microprogrammazione quando quei bus non sarebbero comunque utilizzati (es.
trasferimento diretto in memoria operato dal DMA controller). Quasi tutte le architetture moderne
sono coadiuvate da dispositivi autonomi e sincronizzati che sollevano la CPU da gravosi compiti:
spesso queste collaborazioni avvengono su fasi parallele (es. interrupt controller quando riconosce
l’interruzione, la accoda e la rende con l’id. direttamente alla CPU).
Il livello immediatamente superiore invece è assolutamente delegato al sw ed in particolare al sw di
sistema operativo. Il parallelismo a livello di processo (task) è governato totalmente da uno
specifico SO che ha la capacità di lavorare in multitasking (es. Windows 3.X) o in timesharing (es.
UNIX). In realtà siccome condizione necessaria per SO del genere è la predisposizione dell’hw alla
protezione della memoria per evitare conflitti tra i diversi task, ci sembra che anche da questo punto
di vista l’hw debba essere tenuto doverosamente in considerazione. Il sw comunque garantirà la
concorrenza e il parallelismo tramite l’opportuna duplicazione e virtualizzazione delle risorse hw
più rilevanti come CPU, Memoria principale e alcune periferiche.
Il parallelismo a livello di programma infine costituisce una delle nuove frontiere del mondo
informatico: si tratta di strutturare i programmi e gli algoritmi in essi contenuti in modo che la
risoluzione sia affrontabile e risolvibile con più CPU. In questo caso si pongono numerose questioni
a tutti i livelli e tutte imprescindibili che hanno, di fatto, ostacolato la diffusione di macchine
parallele:
•
strutturazione dell’hardware con n CPU e con una architettura di sincronizzazione specifica
(macchine MIMD).
•
strutturazione del software con metodologie di sviluppo completamente differenziate dalla
norma (algoritmi paralleli).
•
strutturazione del sistema operativo come coordinatore delle due unità hw e sw (SO
distribuiti multiprocessor).
ARCHITETTURE PARALLELE5
Classificazione di Flynn
Nel 1966 Flynn elaborando e sfruttando la sua conoscenza nel mondo dell’informatica descrisse le architetture dei
calcolatori suddividendole in quattro classi:
SISD: Single Istruction Stream, Single Data Stream. Sono le architetture tradizionali, con una sola
unità esecutiva (niente pipeline, niente execution unit multiple) ed una sola memoria.
SIMD: Single Instruction Stream, Multiple Data Stream. Ha un’unica unità di controllo e più unità esecutive. Tutte
però eseguono la stessa istruzione, ma su dati diversi. Un esempio possono essere i calcolatori vettoriali: eseguono la
stessa istruzione (somma, sottrazione, ecc.) ma su vettori di dati (contemporaneamente). Sistemi SIMD attualmente
presenti sul mercato sono Connection Machine (della Thinking Machine Co.) e MP (MasPar & DEC). Le applicazioni
multimediali e di comunicazione di oggi prevedono spesso loop ripetitivi, che utilizzano il 10% o meno del codice
complessivo dell'applicazione impegnando fino al 90% del tempo di esecuzione. La tecnica SIMD consente ad
un'istruzione di eseguire la stessa funzione su blocchi multipli di dati, consente inoltre al chip di ridurre i loop ad
elaborazione intensiva comuni a video, audio, grafica e animazione.
MISD: Multiple Instruction Stream, Single Data Stream. Sono i sistemi basati su pipeline, dove lo stesso dato
attraversa diverse unità esecutive che svolgono compiti diversi. Esempi commerciali sono il Cray-1 e il CDC Cyber
205. Vi sono anche alcuni sistemi fault-tollerance, in cui programmi diversi (ma con la stessa funzione) vengono
eseguiti sugli stessi dati.
MIMD: Multiple Instruction Stream, Multiple Data Stream. Vi sono più unità esecutive, ognuna delle quali ha una
propria memoria locale dalla quale legge il programma da eseguire e i dati su cui operare. In alcuni casi i calcolatori
vettoriali vengono fatti ricadere in questa categoria, poiché ogni stadio della pipeline può essere considerato come un
PE che lavora su dati propri.
5
Da http://multimedia.polito.it/sist-elab-2/aa2001/modulo1/contenuti/materiale-vario/parall_intro.pdf