Università degli Studi di Napoli Federico II



Documenti analoghi
Architettura hardware

Introduzione. Classificazione di Flynn... 2 Macchine a pipeline... 3 Macchine vettoriali e Array Processor... 4 Macchine MIMD... 6

CPU. Maurizio Palesi

L unità di elaborazione pipeline L unità Pipelining

Esame di INFORMATICA

Calcolatori Elettronici. La memoria gerarchica La memoria virtuale

Architettura del calcolatore

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

La memoria centrale (RAM)

Calcolatori Elettronici

La memoria - generalità

Appunti sulla Macchina di Turing. Macchina di Turing

Lezione n.19 Processori RISC e CISC

Calcolatori Elettronici. La Pipeline Criticità sui dati Criticità sul controllo Cenni sull unità di controllo

Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo.

Architettura di un calcolatore

Hazard sul controllo. Sommario

Il Processore: i registri

C. P. U. MEMORIA CENTRALE

Architettura di un calcolatore: introduzione

CALCOLATORI ELETTRONICI A cura di Luca Orrù. Lezione n.7. Il moltiplicatore binario e il ciclo di base di una CPU

Introduzione. Coordinazione Distribuita. Ordinamento degli eventi. Realizzazione di. Mutua Esclusione Distribuita (DME)

LABORATORIO DI SISTEMI

Esercizi su. Funzioni

La macchina di Von Neumann. Archite(ura di un calcolatore. L unità di elaborazione (CPU) Sequenza di le(ura. Il bus di sistema

Laboratorio di Informatica

Organizzazione della memoria principale Il bus

Il Sistema Operativo. C. Marrocco. Università degli Studi di Cassino

Informatica - A.A. 2010/11

Sistema operativo: Gestione della memoria

Ing. Paolo Domenici PREFAZIONE

Coordinazione Distribuita

Struttura del calcolatore

Gerarchia delle memorie

Strutture di Memoria 1

FONDAMENTI di INFORMATICA L. Mezzalira

Laboratorio di Informatica

Calcolatori Elettronici A a.a. 2008/2009

Il processore. Il processore. Il processore. Il processore. Architettura dell elaboratore

Corso di Informatica Generale (C. L. Economia e Commercio) Ing. Valerio Lacagnina Rappresentazione in virgola mobile

Valutazione delle Prestazioni. Valutazione delle Prestazioni. Architetture dei Calcolatori (Lettere. Tempo di risposta e throughput

Unità Periferiche. Rete Di Controllo

INTRODUZIONE I CICLI DI BORSA

Più processori uguale più velocità?

ARCHITETTURE MICROPROGRAMMATE. 1. Necessità di un architettura microprogrammata 1. Cos è un architettura microprogrammata? 4

Lezione 8. La macchina universale

Il memory manager. Gestione della memoria centrale

4 3 4 = 4 x x x 10 0 aaa

Sommario. Definizione di informatica. Definizione di un calcolatore come esecutore. Gli algoritmi.

Memoria Virtuale. Anche la memoria principale ha una dimensione limitata. memoria principale (memoria fisica) memoria secondaria (memoria virtuale)

A intervalli regolari ogni router manda la sua tabella a tutti i vicini, e riceve quelle dei vicini.

Architettura dei calcolatori II parte Memorie

Quinto Homework. Indicare il tempo necessario all'esecuzione del programma in caso di avvio e ritiro fuori ordine.

Corso di Informatica

Gestione della memoria centrale

SISTEMI DI ELABORAZIONE DELLE INFORMAZIONI

Gerarchie di memoria Divide et impera. Gerarchie di memoria La congettura 90/10. Gerarchie di memoria Schema concettuale

LA MOLTIPLICAZIONE IN CLASSE SECONDA

Scheduling della CPU. Sistemi multiprocessori e real time Metodi di valutazione Esempi: Solaris 2 Windows 2000 Linux

Calcolo numerico e programmazione Architettura dei calcolatori

Architettura dei computer

I Thread. I Thread. I due processi dovrebbero lavorare sullo stesso testo

1. Che cos è la multiprogrammazione? Si può realizzare su un sistema monoprocessore? 2. Quali sono i servizi offerti dai sistemi operativi?

Corso di Sistemi di Elaborazione delle informazioni

Architettura del computer (C.Busso)

Architettura hw. La memoria e la cpu

I libri di testo. Carlo Tarsitani

DMA Accesso Diretto alla Memoria

Analisi e diagramma di Pareto

Come visto precedentemente l equazione integro differenziale rappresentativa dell equilibrio elettrico di un circuito RLC è la seguente: 1 = (1)

Prestazioni CPU Corso di Calcolatori Elettronici A 2007/2008 Sito Web: Prof. G. Quarella prof@quarella.

Introduzione. Il principio di localizzazione... 2 Organizzazioni delle memorie cache... 4 Gestione delle scritture in una cache...

Corso di Calcolatori Elettronici I A.A Il processore Lezione 18

Dispensa di Informatica I.1

Introduzione alla programmazione in C

La gestione di un calcolatore. Sistemi Operativi primo modulo Introduzione. Sistema operativo (2) Sistema operativo (1)

Sistemi Operativi IMPLEMENTAZIONE DEL FILE SYSTEM. Implementazione del File System. Struttura del File System. Implementazione

CALCOLATORI ELETTRONICI 29 giugno 2011

SISTEMI DI NUMERAZIONE E CODICI

CPU pipeline 4: le CPU moderne

Contenuti. Visione macroscopica Hardware Software. 1 Introduzione. 2 Rappresentazione dell informazione. 3 Architettura del calcolatore

PROVA INTRACORSO TRACCIA A Pagina 1 di 6

Esempio: aggiungere j

Strutturazione logica dei dati: i file

L architettura di riferimento

esales Forza Ordini per Abbigliamento

Dispositivi di rete. Ripetitori. Hub

Sistemi Operativi. 5 Gestione della memoria

PROCEDURA INVENTARIO DI MAGAZZINO di FINE ESERCIZIO (dalla versione 3.2.0)

NUOVI APPROCCI PER UN MANAGER ALLENATORE : IL PROCESSO DI COACHING

CPU pipeline 4: le CPU moderne

Gestione della Memoria

Pronto Esecuzione Attesa Terminazione

Sistemi Operativi IMPLEMENTAZIONE DEL FILE SYSTEM. D. Talia - UNICAL. Sistemi Operativi 9.1

Laboratorio di Informatica di Base Archivi e Basi di Dati

Organizzazione della memoria

23/02/2014. Dalla scorsa lezione. La Macchina di Von Neumann. Uomo come esecutore di algoritmi

Corso di Informatica Applicata. Lezione 3. Università degli studi di Cassino

9. Memoria Virtuale. 9. Memoria Virtuale. 9. Memoria Virtuale

La manutenzione come elemento di garanzia della sicurezza di macchine e impianti

Transcript:

Università degli Studi di Napoli Federico II Sistemi ad Elevate Prestazioni Prof.re Nicola Mazzocca Anno Accademico 24/25 a cura di: ENRICO RAFFONE LUIGI POSTIGLIONE CARLO POSTIGLIONE 1

. INTRODUZIONE AL CORSO (PRESTAZIONI, AFFIDABILITÀ E SICUREZZA)...3.1 Sistemi ad elevate prestazioni... 3.2 Sistemi affidabili... 4.3 Sicurezza dei sistemi informatici... 4 1. SISTEMI AD ELEVATE PRESTAZIONI...4 1.1 Sistemi Monoprocessore... 4 1.1.1 La tecnica del Pipelining... 4 1.1.2. I limiti del pipeling... 6 1.1.3. Cache dati e cache istruzioni... 8 1.1.4. Le Memorie... 9 1.1.5. Conflitti nei sistemi basati su pipeling - La gestione dei salti... 12 1.1.6. Conflitti nei sistemi basati su pipeling - La gestione del conflitto sui dati... 19 1.1.7. Architetture Superscalari Gestione dei conflitti nella gestione di pipe multiple (vettore delle collisioni)... 26 1.1.8. Conflitti nei sistemi basati su pipeling Gestione delle ISR in sistemi pipelined... 32 1.1.9. Architetture VLIW... 36 1.1.1 I Processori DSP... 37 1.1.11 Un esempio di architettura DSP - Texas Instruments DSP TMS32 C6... 43 1.1.12. Progetto di unità di memoria (Cache, MMU, Virtual Cache)... 53 1.1.13. Dimensionamento con strumenti simulativi di memorie cache... 61 1.2. Architetture Parallele... 64 1.2.1. Classificazione delle architetture avanzate SIMD e MIMD... 64 1.2.2. Modelli analitici per l analisi dei sistemi... 67 1.2.3. Esempi di programmazione parallela... 7 1.2.4. Ambienti per la programmazione di sistemi muticomputer Macchine virtuali PVM architettura di PVM Cluster sistemi GRID... 82 1.2.5. Esempio di programma utilizzando PVM... 85 1.2.6. Architetture a memoria condivisa centralizzata Un protocollo di coerenza delle cache: lo snooping... 9 1.2.7. Architettura dei sistemi multicomputer (topologia di interconnessione reti dirette e indirette) - Esempio di sistema di interconnessione per architetture parallele (Vulcan Sw)... 94 APPROFONDIMENTI...14 Un esempio di architettura superscalare - Il processore Pentium... 14 Caratteristiche principali... 14 Architettura... 15 Pipeline... 16 Istruzioni Semplici e Complesse... 17 Dipendenza dalle risorse... 18 Dipendenza dai dati... 18 Dipendenza dal controllo Presenza di istruzioni di salto... 18 Branch Prediction... 18 Floating Point Unit... 11 Cache... 111 2

. Introduzione al corso (prestazioni, affidabilità e sicurezza) Per capire le finalità del corso e il filo logico che lega le lezioni, strutturiamo schematicamente le unità didattiche in modo da capire i diversi argomenti affrontati. Il corso di Sistemi di Elaborazione è basato su tre moduli fondamentali: - Sistemi ad elevate prestazioni; - Sistemi affidabili; - Sicurezza dei sistemi informatici;.1 Sistemi ad elevate prestazioni La prima grossa novità di questo modulo è che il modello di Von Neumann, che fino ad ora ci ha condotto nella trattazione, risulta essere accantonato per lasciare spazio ad architetture reali che non risultano seguire tale modello. Il modello di Von Neumann assume, quindi, una valenza più logicoastratta che architetturale. Le architetture studiate nei diversi corsi universitari a parte l MC68, non risultano essere architetture reali, almeno per quanto riguarda i processori moderni. Quindi ci occuperemo di architetture reali, caratterizzate da precisi accorgimenti sul parallelismo interno e/o sul clock, tali da permettere il miglioramento delle prestazioni. Da un punto di vista pratico nell ambito dello studio di sistemi ad elevate prestazioni possiamo delineare due diverse soluzioni: - Parallelismo interno: Le architetture che puntano su tale aspetto per incrementare le prestazioni sono: le architetture SuperScalari, i VLIW (Very Long Instruction Word), e i DSP; - Architetture Parallele: caratterizzate dall aumento del numero di processori presenti nel sistema, in modo da aumentare le prestazioni. Si passa, quindi, dalla soluzione monoprocessore (a parallelismo interno) a più processori. Naturalmente è possibile sfruttare tale soluzione secondo modalità diverse, è possibile avere due processori che collaborano attraverso memoria condivisa, in tal caso si parla di sistema multiprocessore (il problema relativo è quello della gestione della memoria condivisa). Altra soluzione è quella dei sistemi multicomputer, caratterizzata da una comunicazione tra i processori basata su messaggi. Ogni processore è dotato di un sistema di I/O con cui connettersi agli altri; in tal caso il problema sarà gestire la comunicazione attraverso link. Una delle problematiche importanti, che distingue le architetture parallele dalle soluzioni monoprocessore, è la gestione dello stato che può essere centralizzato o distribuito. Nel caso dei sistemi a più processori ho certamente uno stato distribuito e la gestione, quindi, risulta sicuramente più complessa rispetto al caso dei sistemi monoprocessore. In più possiamo allargare maggiormente il discorso andando a sottolineare che sicuramente la gestione dello stato nei sistemi multicomputer risulta più critica rispetto al caso dei sistemi multiprocessore. Per meglio capire quali sono le difficoltà nella gestione dello stato possiamo stilare una breve lista delle principali difficoltà nella gestione distribuita dello stato: - Perdita del riferimento temporale, quindi l ordinamento temporale degli eventi; - Perdita della mutua esclusione, ogni sistema può elaborare indipendentemente dagli altri mentre in un sistema centralizzato c è mutua esclusione; - Problema di stallo; - Problema di guasti: devo gestire situazioni in cui solo alcuni delle parti che formano il sistema risultano non funzionanti, mentre il resto continua a funzionare; - Problema di programmazione su sistemi distribuiti, con la possibilità di voler realizzare sistemi single-purpose e multi-purpose. Tutti questi problemi sono problemi che rientrano nel cosiddetto Software di Base. 3

Parte esercitativa relativa al primo modulo del corso: - Tesine (analisi di un processore alla luce della teoria trattata al corso); - Esercizi sul dimensionamento delle memorie (Tool Dinero); - Programmazione su architetture parallele (Multicomputer)..2 Sistemi affidabili La seconda parte del corso cura il problema dell affidabilità. Quando consideriamo un sistema basato su più oggetti replicati lo si fa o per aumentare le prestazioni o per raggiungere una maggiore affidabilità. In questo caso ci occuperemo di affidabilità nei sistemi ferroviari e nei sistemi avionici..3 Sicurezza dei sistemi informatici La sicurezza può essere vista come un aspetto strettamente connesso all affidabilità, molte volte si confonde tendendo a studiare sicurezza ed affidabilità come un unico problema. Circa la sicurezza ci interesseremo di: - Firma digitale, in particolare vedremo diversi esempi pratici di implementazione in sistemi reali; - Flusso documentale. 1. Sistemi ad elevate prestazioni 1.1 Sistemi Monoprocessore Il più importante parametro fra quelli che riguardano le prestazioni dei sistemi (ma non l unico) è la velocità di elaborazione. Per incrementare la velocità di un sistema possiamo agire sia sull hardware che sul software, e, per quest ultimo punto, o sulla fase di programmazione, o su quella di compilazione. Ricordiamo che il ruolo del compilatore è quello di adattare il linguaggio di alto livello alle caratteristiche fisiche della macchina. Se ci limitiamo a considerare architetture a singolo processore, possiamo solo muoverci nella direzione di massimizzare le prestazioni dell unico processore presente. Prima o poi però ci si trova ad avere a che fare con applicazioni e/o requisiti prestazionali che non possono essere in nessun caso soddisfatti mediante la struttura monoprocessore, e, quand anche non esistesse questo limite, è solo naturale pensare di incrementare le prestazioni combinando strutture a nodo singolo per ottenere strutture più potenti. Quindi si passa a considerare le architetture parallele. 1.1.1 La tecnica del Pipelining Concentriamoci per ora solo sui sistemi monoprocessore, dove ci sono più unità che evolvono in parallelo. Per poter effettivamente parlare di unità che evolvono in parallelo bisogna accertarsi che il processo elaborativo possa essere sviluppato da unità che elaborano in modo disgiunto. Per fare questo bisogna riferirsi a modelli che possono essere più o meno complessi. Nell ambito degli studi abbiamo visto due modelli principali, il modello CISC e quello RISC, che differiscono per: - Codici Operativi. Avere più (CISC) o meno (RISC) codici operativi corrisponde ad una maggiore o minore complessità della rete di controllo del processore; - Operandi ammessi dal processore. Per i processori RISC gli operandi per le operazioni aritmetiche sono solo di tipo registro, questo significa che per fare una qualsiasi operazione su di un valore contenuto in memoria bisogna far riferimento al modello load/store, cioè i dati devono essere caricati prima all interno dei registri del processore per poi essere oggetto del calcolo effettivo. 4

- Modi di indirizzamento. Per i processori RISC abbiamo solo indirizzamento immediato e indiretto, in quanto attraverso questi due modi riesco a coprire tutte le operazioni. Naturalmente su questo ha un grosso peso l attività di compilazione. Tutto questo ci fa capire che nei processori RISC è meno problematica l operazione aritmetica rispetto ad un operazione di I/O, naturalmente si può dire questo con qualche riserva in quanto già se ci si pone di fare un operazione in aritmetica floating point, queste considerazioni possono cambiare. Questi sono i presupposti da cui partiamo per costruire un sistema monoprocessore basato su pipe, cioè vogliamo costruire un sistema con pochi codici operativi, caratterizzato da codici di load e store, e con pochi modi di indirizzamento. Quindi vogliamo capire ponendoci questi vincoli come possiamo semplificare l architettura del nostro sistema, poi passeremo a capire se l architettura determinata possa andare bene anche per i processori CISC, quindi processori non caratterizzati dalle tre proprietà delineate. Un architettura che può soddisfare i requisiti espressi rispetta delle fasi principali: 1. IF 2. ID 3. EX 4. MEM 5. WB (prelievo (Fetch), interpretazione (Decode), esecuzione (Execution), Memorizzazione ed eventuale Scrittura dei risultati in memoria (Write Back) 1 ) c è da sottolineare che rispetto alla schematizzazione fatta nei corsi di Calcolatori I e II dove ci si poneva nell ottica di poter fare operazioni coinvolgendo sia i registri del processore sia la memoria, per le ipotesi fatte, prima della fase di esecuzione non c è la fase di preparazione degli operandi. Questa fase è mancante perchè gli operandi saranno disponibili nei registri del processore oppure affinché siano disponibili bisogna eseguire un operazione di load. Le cose più importanti su cui su può agire per migliorare le prestazioni del sistema appena delineato sono: - Aumentare il clock; - Duplicare le risorse hardware (figura 1); - Implementare il parallelismo (all interno) del nodo o microparallelismo. In realtà la frequenza del clock non può essere aumentata indefinitamente, a causa di un limite massimo dovuto alle proprietà reattive; la seconda soluzione può essere applicata solo per alcuni componenti del processore, per evidenti motivi economici e di spazio; si preferisce, dunque, la terza soluzione. MAR MAR MDR MDR PC TEMP i 2 TEMP i 3 ID ALU Registro Stato RC Figura 1 : Schema di un processore RISC LIKE. 1 Alcune istruzioni non necessitano di alcune fasi, ad esempio, ADD R2, R1 non necessita della fase WB. 5

Come vedremo più avanti il microparallelismo può essere implementato via hardware e/o software e, in quest ultimo caso, è possibile agire sulla fase di programmazione, o su quella di compilazione. Si può partire dall idea che se le cinque fasi sono eseguite in modo strettamente sequenziale, ogni unità lavora per una quota parte del (1) (2) (3) (4) (5) tempo di esecuzione della singola i-1 IF ID EX MEM WB istruzione; ad esempio, quando i IF ID EX MEM l istruzione è in fase di i+1 i+2 IF ID IF EX ID interpretazione, le unità associate alle altre fasi sono inattive; il processore tempo non è, quindi, sfruttato al massimo. Quindi per rendere il sistema più veloce è possibile prelevare una nuova istruzione, mentre la precedente è ancora in esecuzione (tecnica del prefetching). Più in generale, è possibile parallelizzare le tutte fasi, implementando la tecnica del pipelining. La tecnica del pipelining permette di diminuire il tempo di esecuzione di un programma, anche se il tempo di attraversamento della singola istruzione non varia. Tempo di attraversamento T A = T IF + T ID + T EX + T MEM + T WB Tempo di risposta T R = max {T IF,T ID,T EX,T MEM,T WB } In particolare se N sono le fasi, in condizioni ottimali, il tempo di esecuzione del programma diventa pari ad 1/N del tempo di esecuzione dello stesso programma, in caso di esecuzione sequenziale; ovvero il throughput (produttività), diventa N volte maggiore rispetto all esecuzione sequenziale. 1.1.2. I limiti del pipeling 1 Limite. (Vincolo di tempo) Queste fasi dette in PIPE funzionano bene se tutte le unità funzionali impiegano più o meno lo stesso tempo. Quindi la prima ipotesi da fare per garantire un buon funzionamento del pipeling è: t i cost Infatti, se il tempo necessario per una delle unità è significativamente maggiore di quello delle altre, l intero processore risulterà condizionato dalle prestazioni di tale unità più lenta (come indicato nell espressione precedente del tempo di risposta): la produttività è legata al tempo massimo. Ora, è naturale che ciò può avvenire solo se le istruzioni eseguibili sono relativamente semplici. Come si vede tale condizione tipica dei sistemi Risc è non, come siamo abituati a pensare, una premessa progettuale, ma una conseguenza architetturale. Un istruzione singola processata da un sistema di questo genere impiegherebbe naturalmente lo stesso tempo che sarebbe necessario per eseguirla in un sistema tradizionale. La velocizzazione risiede nella possibilità di eseguire più istruzioni in parallelo nel modo che si è spiegato: migliora il throughput (produttività). Ad ogni colpo di clock, difatti, sarà sempre disponibile una istruzione nella fase di esecuzione. Se ad esempio il tempo di risposta (o di fuoriuscita dal blocco elaborativo) di una istruzione in un sistema tradizionale è 5*t i, qui diventa t i. t i sarà il tempo impiegato da ciascuna unità funzionale, supposto costante; tale parametro evidentemente può essere migliorato per via elettronica. L unico inconveniente è far andare a regime la pipe (problema dello startup), solo dopo una fase transitoria che dura 5*t i possiamo dire che il tempo di risposta diviene t i. Il parallelismo ottenuto è a livello di istruzione: fino a 5 istruzioni dello stesso programma, prelevate in tempi successivi, vengono eseguite in parallelo. Fino a questo momento il miglioramento è solo di tipo hardware: programmatore e compilatore sembrano non entrare in gioco. 6

2 Limite. (Vincolo tecnologico) Dobbiamo imporre che tra le diverse fasi ci debba essere uno stesso clock, quindi questo segnale dovrà viaggiare attraverso un filo, sorgono quindi problemi di natura elettronica, e principalmente il problema dello skew (allungamento) del clock. Quando la frequenza diviene molto elevata, il clock comincia a deformarsi, e la deformazione è tanto maggiore quanto più lunga è la linea di trasmissione. La linea di trasmissione è un circuito induttivo/capacitivo che tende a filtrare le alte frequenze, ovvero quelle che sono responsabili delle variazioni rapide (transizioni 1 e 1 ) del segnale. Per conseguenza il segnale di clock si addolcisce in corrispondenza delle transizioni. Poiché l impedenza vale ωc, maggiore è la frequenza, maggiore è l impedenza di filtro e più evidente è la deformazione; al limite, il clock si deforma fino a divenire una linea, e gli ultimi elementi della catena delle 5 unità funzionali, che ricevono tutte lo stesso segnale di clock, finiscono col non percepire nulla. Il problema è acuito dalla presenza inevitabile del rumore induttivo. Nelle comunicazioni a grandi distanza si sovracampiona il segnale e si inseriscono bit di controllo per sapere se il segnale è corretto o meno. Figura 2: Circuito induttivo/capacitivo. Figura 3: Lo skew del segnale di clock. Per poter quantomeno limitare questo problema, si fa in modo da avere una linea di trasmissione su cui far viaggiare il clock che sia priva di fenomeni capacitivi (problema di tecnologia elettronica), in più è possibile aggiungere tra ogni coppia di unità dello schema uno stabilizzatore, in termini informatici un registro, che sia in grado di acquisire il dato e di mantenerlo stabile durante l elaborazione. 3 Limite. (Problema di gestione delle istruzioni di salto) Le istruzioni di salto sono utilizzate per cambiare il flusso di esecuzione di un programma; quando si incontra una istruzione di salto bisogna eseguirla completamente (fine fase EX), prima di sapere se il salto deve essere effettuato o meno e, quindi, prima di conoscere l indirizzo della prossima istruzione da prelevare. La pipe, intanto, continua a caricare le istruzioni in modo sequenziale. Tali istruzioni potrebbero essere esatte o meno; se non lo sono bisogna eliminarle dalla pipe (flush della pipe) e caricare le istruzioni esatte, si ha, quindi, un ritardo. Tale ritardo detto branch penality è molto rilevante se si pensa che in un programma si ha un salto ogni volta che si ha un ciclo o un costrutto condizionale (if-then-else/do-while ecc); i salti costituiscono, quindi, il 25% circa delle istruzioni eseguite. Le soluzioni adottate per risolvere questo problema sono varie e saranno affrontate in seguito. Questo problema della gestione delle istruzioni di salto è presente sia nei processori CISC che nei processori RISC, però per i RISC il problema è meno rilevante perché i RISC hanno pochi modi di indirizzamento ed operandi quindi fa operazioni più semplici arrivando prima a capire cosa può accadere, mentre nei CISC c è una maggiore complessità anche nei salti, arrivando dopo a capire il salto. 4 Limite. (Problema di conflitto sui dati) Spesso accade che due istruzioni consecutive cercano di leggere e scrivere contemporaneamente lo stesso registro; ciò potrebbe causare gravi errori. Nei processori CISC i conflitti sui dati sono più probabili, siccome quest ultimi possono utilizzare come operandi per tutte le istruzioni sia registri del processore che le locazioni di memoria. Il conflitto sui dati è confinato alle ultime tre fasi: EX, MEM e WB. 7

5 Limite (Problema della gestione delle interruzioni) In un sistema parallelo sono in esecuzione più istruzioni contemporaneamente diventa, quindi, difficile gestire le interruzioni esterne e garantire che le interruzioni interne siano eseguite in ordine. 6 Limite (Problema di conflitti per l accesso in memoria) ovvero più fasi possono tentare di accedere contemporaneamente alla memoria, per eseguire operazioni di lettura delle istruzioni o scrittura dei dati. La cosa fondamentale sarebbe risolvere ognuno dei problemi elencati nell ambito della singola fase senza coinvolgere le altre fasi, quindi limitando gli effetti negativi alla singola fase isolandola dalle altre. Torniamo ai due modelli CISC e RISC. Possiamo dire che quanto spiegato fino ad ora è relativo al modello RISC, se cerchiamo di applicare gli stessi concetti al modello CISC ci rendiamo conto che nei CISC quanto descritto per funzionare deve subire delle aggiunte, sicuramente bisogna aggiungere la fase di operand assembly alle 5 già presenti, ma altro inconveniente che capita nei CISC è la variabilità dei tempi che occorre impiegare nelle diverse fasi, cosa che certamente non va a porre rimedio al primo limite evidenziato, quindi nei processori CISC avremo una suddivisione ulteriore di ogni singola fase più lunga, in fasi, in modo da ottenere una pipe più lunga ma con un tempo di esecuzione di ogni fase approssimativamente costante. Naturalmente allungare la pipe non risulta un vantaggio a causa dello startup che si allunga e a causa del maggior numero di informazioni da gestire in caso di errori. Anche da un punto di vista elettronico si ha, a parità di circuiti che realizzano un pezzo della pipe, una diversa occupazione nei processori RISC e CISC, questo porta alla possibilità di avere più registri nei processori RISC in quanto c è da realizzare meno stadi rispetto ad un processore CISC. 1.1.3. Cache dati e cache istruzioni Il processore lavora ad una frequenza che è fino a 5 volte più elevata di quella di una macchina tradizionale. Le fasi critiche dal punto di vista del tempo di esecuzione sono evidentemente quelle che si interfacciano con la memoria, cioè quelle che abbiamo indicato con WB e IF. Le memorie, rigorosamente sincrone, devono essere dunque molto veloci. Le memorie più veloci che conosciamo sono le memorie cache. CACHE P Si comprende però come, nel momento in cui si esegue una fase di WB per un istruzione e una IF per un altra, si verifichi un conflitto (contemporaneità di una operazione di lettura e di una di scrittura in memoria). IF ID EX MEM WB IF ID EX MEM... IF ID EX... IF ID... IF... Se quindi utilizziamo una sola cache il modello non funziona perché c è un conflitto sulla 5 fase. Osserviamo tuttavia che la fase WB riguarda la scrittura di dati, mentre la fase IF il prelievo di 8

istruzioni. Possiamo pensare allora di impiegare due cache: una per le istruzioni e una per i dati. Tale limite quindi viene superato mediante una duplicazione architetturale. Delle due cache, quella dei dati è più critica; infatti le istruzioni sono statiche: possono essere solo lette. Processore CACHE ist CACHE dati Figura 4 : Le memorie cache per i dati e per le istruzioni. Invece i dati sono acceduti in lettura e scrittura, e quindi tutte le tecniche di write back, write through ecc. considerate nel corso di Calcolatori 2 riguardano principalmente la cache dati. Si noti che la cosa si complica enormemente in presenza di un processore CISC, le cui istruzioni consentono di operare direttamente su dati all interno della memoria: ci sarà una ulteriore fase tra le fasi 2 e 3 la quale, consistendo di operazioni di lettura e scrittura in memoria, provocherà ulteriori potenziali conflitti con le altre fasi che accedono alla memoria. A tutto quanto detto bisogna aggiungere che senza pipelining si accedeva alla memoria ogni 5*t i, con la pipe si accede in memoria ogni t i, quindi le tecnologie su cui si basano le memorie risultano essere molto importanti nell economia del sistema. 1.1.4. Le Memorie Cerchiamo di capire ora come sono strutturate le memorie e come interfacciamo le memorie ai processori. Lo schema generale da cui partiamo è: R A M Pbus C i Pbus C j ma M M U P ma Fra la cache primaria e l eventuale cache secondaria, e fra quest ultima e la RAM ci saranno opportuni protocolli di bus. La memoria virtuale con la sua unità di gestione (MMU) risiede nel processore. La MMU converte gli indirizzi logici, con i quali ha a che fare il processore, negli indirizzi fisici di memoria. MMU lavora come è noto secondo un criterio di memoria associativa, producendo un indirizzo fisico a partire da una etichetta. Apriamo una parentesi su di un argomento che vedremo in futuro: Virtual cache. È da notare che in molti casi anche le cache moderne lavorano con un criterio di associatività (virtual cache). Sfruttando il Processore Cache dati Primaria Buffer Cache dati Secondaria Figura 5: Le cache dei dati di primo e secondo livello. vantaggio di avere la cache primaria integrata sullo stesso blocco del processore, in tale configurazione si fa in modo che la MMU indichi direttamente dove si trova il dato all interno della cache (se vi è presente). Si fondono cioè le due tabelle associative in una sola, mediante la quale la MMU determina la posizione del dato nella cache. Ovviamente nel caso in cui il dato non si trovi in cache sarà necessario propagare normalmente l indirizzo fino alla cache secondaria, o, alla peggio, fino alla memoria fisica. I sistemi moderni, come rappresentato nello schema ad inizio paragrafo, prevedono generalmente un doppio livello di cache dati: la cache primaria (Cj), che risiede sullo stesso chip del processore, al fine di ridurre le 9

distorsioni (skew) e migliorare la propagazione dei segnali, e la cache secondaria (Ci), più lontana dal processore e più capiente, ma meno veloce della primaria. Tra le due cache dati potrebbe nascere un problema di disallineamento dei dati; per evitarlo si utilizza un bus dedicato che, attraverso una politica di Write Through, garantisce sempre il perfetto allineamento tra le informazioni. Il bus dedicato non causa rallentamento del sistema, siccome il trasferimento dei dati tra le due cache è effettuato utilizzando un buffer: il dato, scritto su C j, è prima bufferizzato e, successivamente, scritto in C i. Riepilogando, i dati vengono scritti nella cache primaria Cj, che è quella più vicina al processore, dunque è più veloce e, fra l altro, non presenta problemi di skew; la coerenza con la cache secondaria viene garantita dalla politica di write through, ma le due scritture possono essere differite grazie all impiego di un buffer. In nessun caso si ha conflitto con il prelievo di istruzioni, che sono contenute in una cache distinta. La velocità di esecuzione di un sistema di calcolo dipende dalla velocità con cui si effettuano gli scambi tra la CPU e la Memoria Principale, in cui dati e programmi sono memorizzati durante l esecuzione. Le memorie dovrebbero essere veloci, grandi e poco costose, ma ottenere queste tre caratteristiche contemporaneamente è impossibile, perciò i moderni sistemi di calcolo utilizzano più memorie ciascuna con caratteristiche e compiti diversi. Processore Cache Primaria Cache ist. Cache Secondaria Memoria Principale Memoria di massa Figura 6 : La gerarchia delle memorie. La memoria di massa (o Memoria Secondaria) è ad accesso sequenziale o parzialmente sequenziale, quindi, il tempo di accesso alla memoria dipende dalla locazione a cui si deve accedere. Le memorie cache e la Memoria Principale sono RAM (Random Access Memory); in particolare la Memoria Principale è una RAM dinamica (DRAM), capace di mantenere i dati solo per un breve intervallo di tempo, mentre le cache sono RAM statiche (SRAM), capaci di mantenere i dati finchè sono alimentate. Una memoria dinamica può essere immaginata come una matrice nella quale ciascun elemento è individuato dalla coppia di indici (i,j). Ogni riga costituisce una parola della memoria e tutte le celle di una riga sono collegate alla linea di parola, pilotata dal decodificatore di riga. Le celle di ogni colonna sono collegate ad un circuito Sense/Write, tramite 2 linee di bit. I circuiti Sense/Write sono collegati al decodificatore di colonna ed al bus dei dati. 1

Durante un operazione di lettura/scrittura l indirizzo, proveniente dal processore, è diviso in 2 parti, la prima utilizzata per selezionare la riga, la seconda utilizzata per selezionare la colonna. linee di bit linea di parola DECODIFICATORE DI RIGA Sense/Write Sense/Write DECODIFICATORE DI COLONNA dati Figura 7 : Organizzazione delle celle di memoria. Prima è applicato l indirizzo di riga, seguito dall impulso del segnale RAS (Row Address Strobe), che seleziona la riga; poco dopo è caricato l indirizzo di colonna, seguito dall impulso del segnale CAS (Column Address Strobe), che seleziona il circuito Sense/Write interessato. Se l operazione è di lettura, l uscita del circuito selezionato è trasferita sul bus dei dati; se, invece, è di scrittura i dati presenti sul bus dati vengono trasferiti nel circuito selezionato. Quando si accede a locazioni di memoria successive, l indirizzo di riga è caricato una sola volta, mentre gli indirizzi di colonna nei cicli di memoria successivi; la frequenza, quindi, aumenta rispetto agli accessi a locazioni casuali. Questo mi porta ad avere tempi di accesso più lunghi tipo 4 volte il tempo di secondo accesso ad un elemento della stessa riga, cioè il tempo per accedere al primo elemento di una riga risulta essere 4 volte il tempo di accesso per avere gli elementi successivi di una stessa riga. Si è detto che le DRAM sono capaci di mantenere i dati solo per un breve intervallo di tempo è, quindi, necessario riscrivere periodicamente (ogni 2 16 ms) il contenuto delle celle, prima che esso venga perso definitivamente; questa operazione è detta refresh. 1K 1K 1K 1K BUFFER (1K) (a) carica BUFFER (1K) BUFFER (1K) (b) Figura 8 : L operazione di refresh. 11

In realtà il refresh fatto sulla singola cella richiederebbe un tempo enorme, risulta perciò conveniente rinfrescare un intera riga per volta. Supponiamo, ad esempio, di avere un indirizzo lungo 2 bit, diviso in due gruppi da 1 bit; ci saranno, quindi 2 2 celle, divise in 2 1 righe e 2 1 colonne (figura 8). Il refresh consiste nel copiare la riga in un buffer e ricopiarla nella posizione iniziale; il tempo di refresh sarà, quindi, proporzionale a 2 1, non più a 2 2. Il buffer può essere realizzato con la tecnologia statica, essendo relativamente piccolo. Una naturale evoluzione del modello consiste nel duplicare i buffer, in modo che mentre una riga caricata nel buffer (o scaricata dal buffer), l altro buffer possa scaricare nella cache (o caricare dalla cache) il suo contenuto. Quando si applica un indirizzo di riga, sia durante un operazione di lettura che di scrittura, l intera riga è rinfrescata. Il circuito di refresh e la CPU sono in competizione per l accesso in memoria; il circuito di refresh ha, però, priorità maggiore. L esecuzione del programma subirà, quindi, un ritardo, che dipende dalla modalità di rinfresco: è possibile rinfrescare tutte le righe e poi far tornare la memoria allo stato di funzionamento normale, oppure rinfrescarle un gruppo per volta, in modo da avere tempi di rinfresco più brevi ma, più frequenti. Il rinfresco provoca degrado delle prestazioni della DRAM, che risultano più lente ( 1 volte in più) delle SRAM, che non necessitano dell operazione di refresh. Alcuni chip incorporano al loro interno il circuito di refresh e perciò sono dette memorie pseudostatiche. 1.1.5. Conflitti nei sistemi basati su pipeling - La gestione dei salti 2 Ritorniamo alle limitazioni che caratterizzano la tecnica del pipelining. La prima limitazione che analizziamo è la gestione dei salti. In presenza di un salto non deve essere sempre prelevata dalla memoria l istruzione successiva, e può non essere facile o possibile determinare subito l istruzione a cui saltare. Per capire questo riprendiamo le fasi del processore: 1. IF 2. ID 3. EX 4. MEM 5. WB Quando il processore preleva una istruzione, non sa che tipo di istruzione ha prelevato finché non la interpreta (fase ID), ma, a questo punto, ne avrà già presa un altra (quella immediatamente successiva). Potrebbe rendersi conto che l istruzione precedente era un salto, dovendo quindi saltare ad un istruzione diversa da quella successiva, rendendo, quindi, il successivo prelievo inutile. L importante, in questa situazione, sarebbe, rendersi conto che l istruzione che sta evolvendo è un salto e che cosa segue questo salto, entro le prime due fasi, in quanto andare ad accorgersi di tale situazione quando l istruzione già si trova nella fase di esecuzione significa che ho già una traccia visibile nel sistema di quanto è accaduto, in quanto è cambiato lo stato della macchina sequenziale. Se abbiamo un processore RISC, abbiamo probabilmente istruzioni di salto più semplici (tipo JNZ dove si va a verificare il solo flag Z), caratterizzate, quindi, da una condizione di salto semplice, in tal caso nella fase di decodifica è già possibile capire se il processore deve o meno effettuare il salto; nel caso in cui invece la condizione di salto risulti essere più complessa allora la valutazione viene ad essere effettuata nella fase EX, sforando le prime due fasi entro le quali ci siamo posti l obiettivo di capire se il caricamento dell istruzione successiva risulta o meno inutile. Questo porta a cercare sempre di avere delle istruzioni di salto con condizioni semplici, così da valutare la situazione entro le prime due fasi del ciclo riportato ad inizio paragrafo. Questo discorso risulta rilevante se si pensa che mediamente il 25% del totale delle istruzioni in un programma è rappresentato dalle istruzioni di salto. Se si paga pegno dovendo gestire l evoluzione di istruzioni che non dovevano essere caricate su ¼ delle istruzioni del programma allora la cosa risulta preoccupante. Per meglio comprendere il problema posto di capire la condizione dell istruzione i di salto entro le prime due fasi del ciclo, facciamo un esempio in cui ipotizziamo che la comprensione del salto 2 Le istruzioni che consideriamo sono a lunghezza fissa, in quanto i sistemi descritti a istruzioni variabili sarebbero improponibili, funzionerebbero malissimo. In tale ipotesi bisognerebbe accedere alla memoria prendere un istruzione capire quanto è lunga, riaccedere alla memoria per caricarne il resto. 12

avviene nella fase di esecuzione, in tal caso troveremo che l istruzione successiva i+1 relativa al ramo then si troverà alla fase di decodifica, mentre quella ancora successiva i+2 sarà in fase di fetch. Consideriamo un esempio, in cui abbiamo supposto che ogni istruzione occupi 4 byte: 76 CMP R1, R3; 8 JEQ 1... 1 MOVE R1, R2... il processore procede nel seguente modo: (1) (2) (3) (4) (5) 8 IF ID EX MEM WB 84 IF ID EX MEM 88 IF ID EX tempo Solo alla fine della 3 fase si conoscerà l indirizzo della prossima istruzione da prelevare. La pipe, intanto, ha continuato a caricare le istruzioni in modo sequenziale e le istruzioni 84 e 88 si trovano rispettivamente nelle fasi di ID e IF; esse, quindi, non hanno modificato lo stato del processore, né quello della memoria. Nel caso in cui il salto non deve essere eseguito la pipe continua a funzionare normalmente; se, però, il salto deve essere eseguito le istruzioni 84 e 88 dovranno essere eliminate (flush della pipe) e bisognerà prelevare l istruzione 1 e successive. Si crea un ritardo, che diminuisce la produttività della pipe, detto branch penalty. E importante sottolineare che si cerca, come già affermato sopra, di confinare la gestione dei salti nelle fasi di IF e ID, quindi è possibile risolverla utilizzando una rete di controllo che riguardi solo l hardware di queste 2 fasi. (1) (2) (3) (4) (5) (6) (7) (8) 8 IF ID EX MEM WB 84 IF ID 88 IF 1 IF ID EX MEM WB 14 IF ID EX MEM 18 IF ID EX 112 IF ID tempo Il problema è risolvibile secondo due approcci fondamentali: - Approccio conservativo: nel momento in cui il processore interpreta una istruzione come istruzione di salto (fase ID di decodifica dell istruzione), ferma la pipe, disabilita la propagazione della istruzione che era stata erroneamente già prelevata, determina l istruzione a cui saltare (fase 13

EX) e la preleva. Insomma con questo metodo conservativo non si fa altro che ritornare al vecchio modello di Von Neumann, cioè si ferma la pipe si capisce se bisogna saltare e a cosa bisogna saltare quindi si ricomincia ad usare la pipe dal caricamento dell istruzione a cui saltare. (1) (2) (3) (4) (5) (6) (7) (8) 8 IF ID EX MEM WB 84 IF 88 1 IF ID EX MEM WB 14 IF ID EX MEM 18 IF ID EX 112 IF ID tempo Ovviamente il prezzo da pagare per il blocco di una pipe è tanto maggiore quanto più lunga è la pipe. Inoltre se la percentuale di salti nel programma è significativa (es. 25%) si ha un notevole sottoutilizzo delle potenzialità della pipe, che viene continuamente bloccata. Una considerazione che è possibile fare in questo primo approccio, è che si butta a priori ciò che sta nella pipe, appena dopo l istruzione di salto, però effettivamente il salto potrebbe anche non esserci, in quanto la condizione potrebbe non essere soddisfatta, quindi si intuisce che è possibile migliorare questo approccio, a spese dell hw, cercando di capire in modo anticipato (fase ID) se l istruzione caricata successivamente va buttata oppure no, tutto ciò in base alla condizione di salto, valutata già nella fase di decodifica, come abbiamo già detto per JNZ ed a inizio paragrafo. In tal caso se il flag è alto, non occorre saltare e quindi l istruzione che si è prelevata nel frattempo è quella giusta. Se invece è basso, si è costretti a bloccare la pipe e ad attendere. Molti ritardi possono essere evitati dal programmatore o dal compilatore: il programmatore può contribuire al buon funzionamento del sistema, scrivendo le istruzioni in un ordine tale da minimizzare le probabilità di stallo della pipe. Nel caso di un costrutto if-then-else, ad esempio, conviene inserire nel ramo then l alternativa più probabile. Il compilatore, da parte sua, può operare vari accorgimenti; ad esempio, siccome un ciclo for è sempre tradotto in un if-then-else, esso deve inserire l alternativa più probabile nel ramo then. Nel caso di due cicli innestati, uno di 1 iterazioni e uno di 1, è conveniente inserire il più lungo all interno, in tal modo si verifica un errore di caricamento pipe ogni 1 iterazioni. Il compilatore può capire se il programmatore ha posizionato i due cicli in un ordine non appropriato e invertirli, nel casi in cui ciò sia algoritmicamente possibile. Un buon compilatore per effettuare le proprie ottimizzazioni deve basarsi, oltre che sull efficienza della pipe, anche su altri fattori, come l organizzazione dei dati nella cache. Se, ad esempio, una matrice è memorizzata nella cache per righe, ed il compilatore inverte l ordine dei due cicli in modo da elaborare la matrice per colonne, si avrà come risultato un utilizzo poco vantaggioso della cache, perché quest ultima sarà costretta a interfacciarsi spesso con la Memoria Principale. I compilatori di uso comune, generalmente, non tengono conto delle caratteristiche dell hardware, ma compilano dapprima per una macchina virtuale, detta PCODE e da questa verso il linguaggio macchina. Tale doppia compilazione è vantaggiosa dal punto di vista dell interoperabilità, perché occorre un compilatore separato per ogni linguaggio verso il PCODE, ma un solo compilatore dal PCODE al Linguaggio Macchina; tuttavia listati in Linguaggio Macchina, così generati, risultano spesso poco efficienti. Branch Delay. Possiamo avere un accorgimento effettuabile in fase di compilazione che ci permette di evitare l approccio conservativo, facciamo un esempio. Abbiamo due istruzioni: a = a + b if (c == ) 14

in fase di compilazione conviene che le due istruzioni siano invertite, perché l istruzione a = a + b deve essere eseguita comunque, indipendentemente alla destinazione del salto determinata dalla if. Quindi sicuramente non si commette un errore eseguendo la somma a valle dell istruzione condizionale, e si riduce la perdita di tempo che potrebbe conseguire da un eventuale blocco di pipe, naturalmente questo è un caso particolare perché vale la proprietà commutativa, ma in generale la cosa non è sempre fattibile. Quindi qualora non sia possibile invertire una istruzione if con quella che la precede, il compilatore potrebbe decidere di mettere subito dopo la if una nop, istruzione che non ha alcun effetto e quindi non è mai errato inserirla nella pipe. In questo modo si può operare senza considerare alcun tipo di approccio. Possiamo aggiungere che se stiamo considerando un istruzione di salto in un processore CISC con una condizione di salto elaborata, allora il numero di nop che bisogna inserire a seguito dell istruzione di salto risulta essere superiore a 1, in quanto ricordiamo che la pipe per i CISC è più lunga e la valutazione della condizione coinvolge più fasi oltre le prime due, di caricamento e decodifica. Nella realtà questo tipo di accorgimento non viene utilizzato, ma ci si affida più agli accorgimenti visti precedentemente, quindi si ottimizza il codice considerando di inserire nel ramo then le istruzioni statisticamente più probabili. Questa scelta risulta accettabile, fin quando la pipe è breve, se pensiamo che il 25% del nostro progr. è rappresentato da istruzioni di salto, e il 2% di queste istruzioni di salto generano errore nella pipe, allora l errore che porta a fermare la pipe è del 5%, il costo relativo a questi stalli è accettabile fin quando la pipe è breve, se la pipe risulta essere più lunga la cosa diviene più pesante da sostenere. - Approccio Ottimistico. (Branch Prediction) E quello più complicato e maggiormente diffuso. In questo caso tentiamo di fare una previsione su quale sia il ramo da eseguire in una istruzione condizionale. Consideriamo il seguente esempio: 1 if condizione then 14 else 112 Abbiamo supposto che ogni istruzione occupi 4 byte; il ramo then segue immediatamente il ramo if, mentre il ramo else è localizzato all indirizzo 112 e quindi la sua esecuzione richiede un salto. Vorremmo cercare di indovinare quale dei rami debba essere selezionato, ovvero se l istruzione conseguente la 1 è la 14 o la 112. Normalmente non si può conoscere questa informazione finché non si è arrivati a valutare la condizione di salto. Vogliamo con questo metodo avvicinarci quanto più possibile alla realtà riducendo al minimo gli errori nella pipe. Prima di approfondire questo discorso sulla tecnica di branch prediction, bisogna sottolineare che l implementazione di una metodologia di predizione, risulta essere fortemente legata alle scelte effettuate in fase di progettazione, la documentazione disponibile in rete spesso non chiarisce come effettivamente funzioni la predizione implementata in uno specifico processore. Molte volte solo chi si è occupato direttamente della fase di progettazione di tale processore conosce in dettaglio i meccanismi di gestione dei diversi registri creati allo scopo, quindi chi si pone a capire come funzionino determinati meccanismi può spiegarseli solo attraverso un attenta analisi del processore e prove sperimentali. Casi di questo tipo sono i processori Intel dal Pentium in poi fino ad arrivare al Pentium IV. C è da sottolineare che in generale esistono due principali tipi di branch prediction: il tipo simmetrico, dove la gestione della predizione rispetta un equo bilanciamento tra predizione di salto e predizione di non salto, su questa tipologia abbiamo la trattazione a seguito e la nota a fine paragrafo che espone come tale teoria viene portata avanti secondo i classici testi di J. Hennessy & D. A. Patterson e di A.S. Tanenbaum. Poi abbiamo il tipo asimmetrico, secondo cui nella 15

predizione si propende più verso uno dei casi (salto e non salto) rispetto all equità del caso simmetrico. Rimandiamo agli approfondimenti dove viene trattata la tecnica di branch prediction implementata nel Pentium (asimmetrica), e come tale tecnica differisca dalla tecnica che stiamo per trattare (simmetrica). Torniamo al nostro esempio. La prima volta che viene prelevata l istruzione 1, non si può sapere se comporterà un salto oppure no. Si adotta allora il modello conservativo, prelevando l istruzione seguente (14). Tale decisione può rivelarsi giusta o sbagliata. Se si sbaglia (si doveva prendere il ramo else, 112) si paga il relativo pegno, ma che dire se in seguito si ripresenta l istruzione 1? Ovviamente non desideriamo sbagliare di nuovo. Il processore allora fa così: dopo aver sbagliato alla prima esecuzione dell istruzione 1, mette in una tabella associativa, la Branch Prediction Table, l indirizzo 1 seguito dall indirizzo 112, come illustrato nella figura a destra. In tal modo, quando si ricarica l istruzione 1 il program counter accede in parallelo alla tabellina e preleva l istruzione 112, che è più probabilmente quella corretta, invece della 14. Se l istruzione 1 è assente nella tabellina nel program counter si memorizza come di consueto l indirizzo 14. 1 112 La situazione si complica in presenza di due o più cicli for innestati. Ad esempio: for i 92 CMP JMP for j CMP 1 JXX 14 112 JMP 92 Il processore arriva fino all istruzione 1, e accede in parallelo (con lo stesso indirizzo) alla tabellina, per vedere se non si tratti per caso di un istruzione di salto di cui è già nota la destinazione più probabile. La prima volta non trova tale informazione, ed esegue (correttamente) l istruzione 14. Poiché non ha sbagliato, non scrive nulla nella tabella. Lo stesso si ripete per la seconda, la terza,, l (n-1)-esima iterazione, sempre non sbagliando e sempre senza leggere né scrivere nulla nella tabella. Il problema si verifica in corrispondenza dell n-sima e ultima iterazione del ciclo interno: il processore carica l istruzione sbagliata, se ne accorge e inserisce una riga nella tabella. Si ritorna quindi nel ciclo esterno, e da qui nuovamente nel ciclo interno. Da notare ora che il processore commette subito un errore, saltando all indirizzo 112, cosa S. Debole N.S. Debole S. Forte N.S. Forte Figura 9: I 4 stadi del Branch Prediction. 1 112 che invece andrebbe fatta solo in corrispondenza dell ultima iterazione del ciclo interno. La previsione quindi andrebbe ulteriormente guidata. I processori moderni evolvono fra quattro stati descritti da due bit: NON SALTARE (FORTE), NON SALTARE (DEBOLE), SALTA (FORTE) e SALTA (DEBOLE). Il processore lavora sempre nello stato non salta forte fin quando c è il primo errore (cioè fin quando deve saltare). La prima volta che il processore sbaglia memorizza la coppia di indirizzi nella tabella ed entra nello stato non salta debole. La volta seguente il processore non effettua di nuovo il salto, 16

appena memorizzato nella tabella e controlla cosa accade. Se l informazione contenuta nella tabella (i 2 bit) era giusta non subisce ritardi ed entra nello stato non salta forte ; se l informazione era sbagliata la pipe subisce ritardo e il processore entra nello stato salta debole. La volta seguente il processore effettua il salto memorizzato nella tabella, ma controlla cosa accade; se il salto non doveva essere eseguito va nello stato non saltare debole, altrimenti va in salta forte. Seguendo la stessa logica, quando il processore si trova in uno degli stati forti, se la predizione è giusta rimane nel suo stato, altrimenti passa nel corrispondente stato debole. Le 4 condizioni di salto vengono codificate mediante 2 bit posizionati accanto ad ogni riga della tabella. Grazie a tale accorgimento, i salti errati non vengono eliminati del tutto, ma notevolmente ridotti. Consideriamo cosa accade nell esempio illustrato poco sopra. La prima volta che si esegue il ciclo interno non si sbaglia. Ciclo 1 volta, 1 e success.iteraz. 1 niente in tabella All ultima iterazione si sbaglia e la tabella viene opportunamente caricata con un non salta debole. Ciclo 1 volta, ultima iteraz. 1 112 NSD Dopo il salto al ciclo esterno, viene eseguito per la seconda volta il ciclo interno. Il processore non sbaglia, perché, in base all informazione che trova in tabella, non salta all istruzione 112. Si passa quindi allo stato non saltare forte. Ciclo 2 volta, prima iteraz. 1 112 NSF Benché la riga non venga cancellata dalla tabella (poiché non si può escludere che si debba effettivamente fare un salto in un momento successivo se lo si è fatto almeno una volta in un momento passato) il salto che essa suggerisce non sarà preso in considerazione alla successiva iterazione. Proseguendo con la seconda iterazione si resta nello stato non saltare forte : Ciclo 2 volta, seconda iteraz. 1 112 NSF e tale rimane per le successive iterazioni. All ennesima iterazione della seconda esecuzione il processore sbaglia a non saltare all istruzione 112; rimedia quindi all errore e promuove lo stato in tabella, da non saltare forte a non saltare debole : Ciclo 2 volta, ultima iteraz. 1 112 NSD Si noti dunque che il processore sbaglia alla fine della prima esecuzione e alla fine della seconda. Supponendo allora che il ciclo esterno sia di 1 iterazioni e quello interno di 1, il processore sbaglia una volta per ciascuna esecuzione del ciclo interno (sull ultima iterazione, un errore inevitabile), più una volta sul ciclo esterno, e quindi 1 + 1 = 11 volte soltanto, su 1*1 = 1 iterazioni. Da questo si capisce anche il perché i numero di cicli esterni deve essere minore del numero di cicli interni, in quanto come visto dal conteggio degli errori nella pipe, se ho più cicli esterni commetto più errori rispetto al caso opposto. Questo sistema è ottimo per cicli for innestati, come pure per istruzioni condizionali per le quali l eventualità else sia la più probabile, poiché si adatta automaticamente a tale probabilità; l approccio conservativo risulterebbe piuttosto inefficiente in questo caso. Non si può dire nulla a priori invece se l istruzione a cui saltare può essere determinata solo a tempo di esecuzione. Per 17

applicazioni d interesse rilevante, la tabella associativa usata nell approccio ottimistico dovrebbe avere dimensioni significative, dell ordine delle centinaia di Kbyte. La predizione dei salti risolve errori sistematici di programmi ciclici, programmi di questo tipo sono classici dei Sistemi Operativi. Possiamo capire che comunque anche la branch prediction non è infallibile, è possibile scrivere programmi che sono il top dell inefficienza per il processore anche in presenza di predizione di salto, facciamo un esempio di un programma che ripetuto ciclicamente crea grossi problemi: IF a = 1 THEN a = ELSE a = 1 Analizziamo la predizione con un solo bit di stato, poi vediamo quella con due bit: Partiamo dall ipotesi in cui a = 1 Predizione i Predizione i+1 Errore 1. a=1 Then a= THEN NO 2. a= Else a=1 THEN ELSE SI 3. a=1 Then a= ELSE THEN SI 4. a= Else a=1 THEN ELSE SI 5. a=1 Then a= ELSE THEN SI Si vede che abbiamo, dopo il primo ciclo, un errore ad ogni ciclo. Con il doppio bit, il problema viene risolto al 5%: Errore Stato dei due bit 1. a=1 Then a= NO Entry non in tabella associativa 2. a= Else a=1 SI Non Salta Debole 3. a=1 Then a= NO Non Saltare Forte 4. a= Else a=1 SI Non Saltare Debole 5. a=1 Then a= NO Non Saltare Forte 6. a= Else a=1 SI Non Saltare Debole 7. a=1 Then a= NO Non Saltare Forte 8. a= Else a=1 SI Non Saltare Debole 9. a=1 Then a= NO Non Saltare Forte Si nota molto chiaramente che anche se riduciamo il numero di errori con i due bit abbiamo comunque un elevata inefficienza per il processore. N.B.: La predizione a seguito del primo errore introduce una nuova entry nella tabella associativa che riporta l indirizzo dell IF affiancato dall indirizzo della predizione, nel nostro caso l indirizzo di predizione è quello di ELSE. Nota secondo trattazione Patterson/Hennessy e Tanenbaum L approccio alla tecnica di Branch Prediction considerato nell ambito dei due testi di riferimento, risulta essere sicuramente più modulare rispetto a quanto spiegato in questo paragrafo, dove si fa riferimento già ad una tabella associativa e a due bit di predizione. Mentre nei due testi, almeno in prima battuta, non si fa alcun riferimento all implementazione di una branch table, ma solo ai due bit di riferimento attraverso cui gestire i salti. La cosa può essere motivata considerando che la nostra trattazione risulta orientata ad un processore reale (vedi Approfondimento Pentium), mentre i due testi considerati portano avanti una costruzione didattica dell argomento, in particolare Patterson fa riferimento all architettura DLX, caratterizzata dall ipotesi di valutazione della 18

condizione di salto già effettuata nella fase di decodifica, mentre Tanenbaum non chiarisce un implementazione reale della tecnica proposta, ma fa un ipotesi esemplificativa supponendo di disporre già degli indirizzi a cui eventualmente saltare, come se tali branch fossero incondizionati con l indirizzo di salto già codificato nell istruzione. In ogni caso entrambi i testi risultano concordare nella gestione dei due bit di predizione secondo il seguente automa a stati finiti, descritto dallo schema. Descrizione del funzionamento: Lo stato iniziale che corrisponde all assenza dell entry in tabella associativa risulta essere lo stato Predict no branch ( Non salta forte ) codificato dai due bit, fin quanto non si verifica un errore si resta nello stesso stato, appena si verifica il primo errore si passa a Predict no branch one more time ( Non salta debole ), quindi se si verifica ancora un errore (bisognava saltare) si cambia di opinione, si passa allo stato Predict Branch corrispondente al nostro Salta Forte. Analogo procedimento è possibile per quanto riguarda la sequenza Predict branch ( Salta forte ), Predict branch one more time ( Salta debole ) e Predict no branch ( Non salta forte ). Ultima osservazione che resta da fare circa il meccanismo descritto nei due testi, risulta essere la simmetria dell automa a stati finiti rispetto a quanto esposto negli Approfondimenti, circa il processore Pentium. Nel caso in figura, infatti, abbiamo un equa ripartizione tra salto e non salto, poiché solo dopo due errori consecutivi si verifica il cambio di opinione. 1.1.6. Conflitti nei sistemi basati su pipeling - La gestione del conflitto sui dati Se due istruzioni consecutive tentano di accedere contemporaneamente ad uno stesso registro/locazione di memoria, come già accennato in precedenza, si generano conflitti sui dati, classificati in conflitti Read after Write (R/W) e conflitti Write after Read (W/R). Le operazioni R/R e W/W, infatti, non causano problemi: la prima perché la lettura non modifica lo stato dei registri/memoria, la seconda perché conta solo la scrittura operata dalla seconda istruzione. Consideriamo un esempio di conflitto R/W: i R2 = R1 + R3 i+1 R4 = R2 + R5 DEMUX R2 R5 (1) (2) (3) (4) (5) i IF ID EX MEM WB i+1 IF ID ID EX ALU 19

Quando l istruzione i+1 giunge nella fase ID essa deve prelevare il valore di R2; tale operazione, però, non è possibile siccome il nuovo valore non è stato ancora memorizzato nel registro R2; la pipe, quindi, entra in stallo. Si nota tuttavia che, in corrispondenza della ID della seconda istruzione, la fase EX della prima è già terminata; quindi, il risultato del calcolo per R2 è stato già determinato, questo ci viene garantito dal fatto che ogni fase ha la stessa durata e che le operazioni sono tutte su registri. Per risolvere questo conflitto si utilizza la tecnica dell anticipo degli operandi (Operand Forwarding). Il conflitto può essere risolto ricorrendo allo schema indicato sopra. Il dato calcolato dall ALU viene inviato dal demultiplexer contemporaneamente ai registri R del processore, ma anche alla stessa ALU, così da renderlo subito disponibile per il passo successivo. La tecnica dell anticipo degli operandi consiste nel creare un canale (hardware), che renda i risultati disponibili appena l ALU li calcola. In questo modo durante il 4 ciclo di clock il nuovo valore sarà memorizzato e, contemporaneamente, utilizzato dalla i+1, senza che la pipe entri in stallo. Questa tecnica è realizzabile solo se si opera su registri del processore ed ogni fase impiega un solo ciclo di clock (processori RISC). Nel caso di istruzioni, in cui gli operandi sono locazioni di memoria (processori CISC), l approccio conservativo impone di fermare la pipe finché non si è sicuri che l operazione concernente la locazione di memoria sia terminata (fase WB). C è da sottolineare in questo caso che al di là delle discussioni che stiamo per iniziare sulla gestione delle istruzioni per evitare lo stallo, se troviamo una istruzione come l istruzione i e il dato richiesto MEMA non risulta essere in cache, c è un cache miss e la pipe comunque si ferma, quindi c è uno stallo. In tal caso paghiamo molto, perché se la cache è 8-5 volte più veloce della RAM, allora probabilmente si blocca la pipe per tutti i quanti di tempo. La memoria è un dato molto critico in un sistema. Ma ritorniamo al caso in cui non consideriamo stalli a causa di cache miss, dobbiamo gestire il conflitto sui dati in questo esempio, il problema da risolvere è quello visto prima per quanto riguarda i soli operandi registro. i R1 = MEMA; i+1 R2 = R1 + R3; i+2 R4 = R2 + R5; i+3 R2 = R6 + R7; i+4 R4 = R2 + R4; R1= MEMA; R2=R1 + R3; R4=R2 + R5 R2=R6 + R7; R4=R2 + R4; Utilizzando le proprietà associativa e commutativa è possibile evitare stalli della pipe cambiando l ordine delle istruzioni. La proprietà associativa consente di associare in un solo gruppo le istruzioni dipendenti tra loro; la commutativa, invece, permette di individuare i gruppi di istruzioni invertibili, ovvero la cui inversione non causa errori di esecuzione. Tutto ciò è implementato da hardware posto prima della fase di EX. Se tali proprietà non valgono la pipe va in stallo. Per evitare stalli nel caso in cui le due proprietà descritte non siano praticabili, i processori implementano la tecnica dell Internal Forwarding; la quale consiste nell ibernare le istruzioni, che non possono essere eseguite subito, perché in conflitto con le precedenti, per iniziare ad eseguire le successive. Questa modifica nell ordine dell esecuzione delle istruzioni non dà problemi, purché le istruzioni coinvolte non agiscano sugli stessi dati. In questo modello quindi le istruzioni vengono prelevate in sequenza, ma non necessariamente eseguite in sequenza, badando comunque che sia sempre preservata la piena coerenza fra l esecuzione nell ordine naturale delle istruzioni e quella che scaturisce nella pipe in seguito ad eventuali ibernazioni. Tali accorgimenti possono modificare anche il meccanismo delle interruzioni, se ho per esempio un overflow, anche l ordine naturale delle interruzioni ne viene di conseguenza alterato. 2