Microarchitetture ad Alte Prestazioni: Gestione delle «Control Dependencies» Davide Bertozzi.

Slides:



Advertisements
Presentazioni simili
Architetture dei Calcolatori (Lettere j-z) Il Processore (2)
Advertisements

Criticità sui dati Consideriamo una sequenza di 5 istruzioni
Criticità sul controllo
Criticità sui dati (esempio da fare on line)
Criticità sul controllo
Criticità sul controllo
Architetture dei Calcolatori (Lettere j-z ) Il Processore
Calcolatori Elettronici Introduzione al Pipelining Francesco Lo Presti Rielaborate da Salvatore Tucci.
Università degli Studi di Napoli “Federico II” Facoltà di Ingegneria Dipartimento di Informatica e Sistemistica Corso di Sistemi ad elevate prestazioni.
Arch. Elab. - S. Orlando 1 Esercitazione su Instruction Level Parallelism Salvatore Orlando.
Calcolatori Elettronici Introduzione al Pipelining
Calcolatori Elettronici Il Processore
Calcolatori Elettronici Il Processore (2)
Selezione avversa nella selezione del personale. Il problema Al momento dell’assunzione è molto costoso avere a che fare con lavoratori non adatti al.
IL PROCESSORE I MICROPROCESSORI INTEL Il microprocessore è un circuito integrato dotato di una struttura circuitale in grado di effettuare un determinato.
Unità di apprendimento 6 Dal problema al programma.
Fondamenti di Informatica - D. Talia - UNICAL 1 Fondamenti di Informatica FONDAMENTI DI INFORMATICA Domenico Talia
Huffman Canonico: approfondimento. Come abbiamo visto, Huffman canonico ci permette di ottenere una decompressione più veloce e con un uso più efficiente.
Laboratorio di Architettura Degli Elaboratori1 PSPICE – Circuiti sequenziali.
Elementi fondamentali dell’ Architettura di di un elaboratore elettronico.
.  I tipi di dati non primitivi sono gli array, le struct e le union.  Gli array sono degli aggregati di variabili dello stesso tipo.  La dichiarazione.
CONTROLLO DELLA CONCORRENZA
© 2007 SEI-Società Editrice Internazionale, Apogeo
Universita’ di Milano Bicocca Corso di Basi di dati 1 in eLearning C
Il Sistema Operativo Gestione dei Processi
Universita’ di Milano Bicocca Corso di Basi di dati 1 in eLearning C
Architettura e funzionalità
Statistica Prima Parte I Dati.
Algoritmi Avanzati a.a.2015/2016 Prof.ssa Rossella Petreschi
PROGRAMMAZIONE SHELL -
LE ARCHITETTURE NON VON NEUMANN
Microcontrollori e microprocessori
7. Strutture di controllo Ing. Simona Colucci
I microprocessori Il microprocessore è un circuito integrato costituito da silicio. Il microprocessore svolge fondamentalmente due funzioni: sovraintende.
Algoritmi Avanzati a.a.2013/2014 Prof.ssa Rossella Petreschi
IL CONCETTO DI ALGORITMO
Dal problema al processo risolutivo
Cammino dei Dati (Datapath)
Unità di apprendimento 7
I vincoli di integrità Alcuni aspetti della realtà NON possono essere modellati solamente con entità, attributi e relazioni, per esempio i vincoli di integrità.
I BUS È un insieme di fili conduttori che permette il passaggio di dati tra le varie periferiche del pc.
Tipo di dato: array Un array è un tipo di dato usato per memorizzare una collezione di variabili dello stesso tipo. Per memorizzare una collezione di 7.
Architetture non Von Neumann
PROGRAMMAZIONE BASH – ISTRUZIONE IF
Universita’ di Milano Bicocca Corso di Basi di dati 1 in eLearning C
analizzatore di protocollo
Introduzione L’8254 è un interval timer event/counter, progettato per risolvere i problemi del controllo del timing, comuni ad ogni microcomputer. E’ costituito.
Controllo e microprogrammazione
Processi e Thread Meccanismi di IPC (1).
Psicologia dell’apprendimento
Programmare.
© 2007 SEI-Società Editrice Internazionale, Apogeo
Instruction Level Parallelism
Vari e semplici programmi.
Scheduling in Linux (Kernel 2.4 e 2.6)
Ricorsione 16/01/2019 package.
Esercitazione su Instruction Level Parallelism
Algoritmi Avanzati Prof.ssa Rossella Petreschi
Gli archivi e le basi di dati
Esercitazione 8 Laboratorio di Architetture degli Elaboratori I
Teoria della computabilità
Parti interne del computer
Processi decisionali e funzioni di controllo
Unità 1 Programmi base.
Scheduling (Schedulazione)
Analisi ammortizzata Lezione n°2
La programmazione strutturata
Progetto del processore e supporto del processore al SO (interruzioni – eccezioni) Salvatore Orlando.
Relazioni tra CPU e Memoria e Dispositivi I/O
Algoritmi.
Transcript della presentazione:

Microarchitetture ad Alte Prestazioni: Gestione delle «Control Dependencies» Davide Bertozzi

Acknowledgement Most of this material is inspired/adapted/translated from the online courses of prof. Onor Mutlu at CMU: https://people.inf.ethz.ch/omutlu/teaching.html

Architettura di Riferimento Notare che con questa implementazione il branch è risolto pienamente solo alla fine della fase di MEM

Branch Hazards Ipotesi: Solo alla fine di MEM so decidere quale sarà il nuovo program counter! Branch Penalty Potenziale: 3 cicli! Intanto tre nuove istruzioni sono già entrate nella pipeline! La penalità aumenta in modo significativo con pipeline lunghe e con processori che fanno il fetch di più istruzioni contemporaneamente.

Branch Hazards Terminologia: Branch target (address): indirizzo a cui il branch salta Branch taken: il controllo viene trasferito ad un indirizzo diverso (target) da quello dell’istruzione successiva Branch not taken: viene eseguita l’istruzione successiva Branch prediction accuracy: percentuale di branch accuratamente predetti Fall-Through Path: percorso preso se il branch è NOT TAKEN CHE FARE IN PRESENZA DI UN BRANCH? Quali istruzioni caricare? Che fare con le istruzioni già caricate nel caso si rivelino essere quelle sbagliate? LA GESTIONE DI UN BRANCH PUO’ RIGUARDARE L’HARDWARE, IL SOFTWARE OPPURE ENTRAMBI

Control Dependencies E’ essenziale mantenere la pipeline piena con la corretta sequenza delle istruzioni “dinamiche” (a tempo di esecuzione). Soluzioni potenziali per gestire salti condizionali (che sono delle control-flow instructions): Stallare la pipeline finchè il branch non è risolto Predirre il prossimo fetch address (branch prediction) Branch ritardati (branch delay slot) Fare qualcos’altro (fine-grained multithreading) Eliminare le istruzioni di controllo (predicated execution) Fare il fetch da entrambi i percorsi possibili (multipath execution), se i relative indirizzi sono noti

Soluzione banale…ma non così banale! Idea: stallare la pipeline finchè il branch non viene risolto Time add $s4, $s5, $s6 Beq $s1, $s2, 40 add $s4, $s5, $s6 STALL Beq $s1, $s2, 40 add $s4, $s5, $s6 STALL STALL Beq $s1, $s2, 40 add $s4, $s5, $s6 STALL STALL STALL Beq $s1, $s2, 40 add $s4, $s5, $s6 or $s7, $s8, $s9 STALL STALL STALL Beq $s1, $s2, 40 or $s7, $s8, $s9 STALL STALL STALL Ma c’è un problema enorme: Per mettere uno stallo dopo un branch, il processore dovrebbe sapere che sta facendo il fetch di un branch! Ma non lo può sapere se non dopo la fase di decode! Se proprio vogliamo seguire questo approccio, Il compilatore deve mettere tre NOP (no-operation) dopo ogni branch (se l’ISA prevede il NOP, oppure istruzioni equivalenti come sll $zero, $zero, 0), oppure si fa una invalidazione e la macchina inserisce 2 stall. In alternativa, occorre usare la pipeline al 50% (fetch, poi capisci se è un branch,…)

Si Può Fare Meglio? Perchè non predirre sempre che next_PC = PC+4 così da poter fare il fetch di istruzioni ad ogni ciclo? E’ una predizione saggia? Cosa perdi se la predizione non è corretta? ~20% del mix di istruzioni è una istruzione di controllo ~50 % del “forward” control flow (i.e., if-then-else) è taken ~90% del “backward” control flow (i.e., loop back) è taken Complessivamente, circa il 70% dei salti è taken e il 30% not taken (Lee and Smitch, 1984) Ci attendiamo “next_PC = PC+4” circa nell’86% delle istruzioni in fetch, ma che ne è del rimanente 14%?

Next_PC = PC + 4 Idea: Predirre sempre che la prossima istruzione della sequenza è la prossima istruzione che deve essere eseguita In pratica, nessuna predizione, perchè questo è quello che accade di solito, salvo poi implementare il recovery da misprediction. Come si può migliorare? Idea: Massimizzare la probabilità che la prossima istruzione della sequenza sia davvero la prossima istruzione da eseguire. Come? Software: cambia il control flow graph in modo che la prossima istruzione più probabile sia nel not-taken path del branch Hardware: si può fare qualcosa di simile mediante una Trace Cache, ma non la vediamo in questo corso.

Soluzione Software Software: compilare un control flow graph tale che la prossima istruzione più probabile sia sul percorso “not-taken”. Supponiamo che grazie al «profiling del codice», Il compilatore sappia le probabilità di taken e not-taken Beq $s0,$s1,.. taken Not-taken Beq $s0,$s1,.. Codice se $s0 = $s1 Codice se $s0 ≠ $s1 taken Not-taken Codice se $s0 = $s1 Codice se $s0 ≠ $s1 80% 20% Bne $s0,$s1,.. taken Not-taken Codice se $s0 ≠ $s1 Codice se $s0 = $s1 Nome della tecnica: «Profile-guided code positioning» 20% 80%

Next_PC = PC + 4 In quale altro modo si può migliorare la performance? Idea: sbarazzandosi delle istruzioni di controllo (o meglio, minimizzandole) Come? Sbarazzandosi delle istruzioni di controllo non necessarie  combinando i predicati (predicate combining) 2. Convertendo dipendenze di controllo in dipendenze dati  predicated execution

Predicate Combining Condizioni complesse sono di solito convertite in branch multipli: if ((a == b) && (c < d) && (a > 5000)) { … } Vengono fuori almeno 3 salti condizionali…. Problema: questo aumento il numero di control dependencies, e il loro potenziale penalty. Idea: combinare le condizioni in modo da esporre solo un branch In pratica (assumiamo nuove pseudo-istruzioni CMPEQ, CMPLT, CMPGT, e una sintassi estesa per le istruzioni and e beq): CMPEQ condizione1, a, b #confronta a e b, e se uguali setta condizione1 a 1 CMPLT condizione2, c, d #se c è inferiore a d, setta condizione2 a 1 CMPGT condizione3, a, 5000 #se a è maggiore di 5000, setta condizione3 a 1 and condizione4, condizione1, condizione2 #accumula le condizioni and condizione 4, condizione3, condizione4 #accumula le condizioni BEQ condizione4, $zero, OFFSET #salta se tutte le condizioni sono verificate

Predicate Combining Condizioni complesse sono di solito convertite in branch multipli: if ((a == b) && (c < d) && (a > 5000)) { … } Vengono fuori almeno 3 salti condizionali…. Problema: questo aumento il numero di control dependencies, e della loro potenziale penalty. Idea: combinare le condizioni in modo da esporre solo un branch Alcuni ISA mettono a dispozione dei condition registers per memorizzare i predicati (es., IBM RS6000, IBM POWER). Un predicato corrisponde con il settaggio di un bit (vero/falso) del condition register Supporto per fast condition manipulation così da risparmiare istruzioni (es., and) Un singolo branch controlla il valore dei predicati combinati * Vantaggio: meno branch nel codice  meno mispredictions/stalls * Svantaggio: esiste la possibilità che si compia lavoro non necessario -- se il primo predicato è falso, non c’è alcun motivo di elaborare gli altri predicati, eppure questi vengono elaborate.

Predicated Execution Idea: convertire dipendenze di controllo in dipendenze dati Assumiamo di avere una “Conditional Move instruction CMOV”: CMOV condizione, R1  R2 La cui semantica è: “R1 = (condizione == true) ? R2 : R1” Utilizzata nella maggior parte degli ISA moderni (x86, Alpha) (presuppone un’altra istruzione: “CMPLT condizione, R1, value” La cui semantica è “condizione = (R1 < value)? 1:0” Esempio di codice C compilato con branch oppure CMOV if (a < 5) {b = 4;} else {b = 3;} slti $s0, $s2, 5 beq $s0, $zero, Else li $s3, 4 J Exit Else: li $s3, 3 Exit: …. CMPLT condizione, a, 5 CMOV condizione, b  4; CMOV !condizione, b  3;

Predicated Execution Elimina i branch  permette “straight line code” (cioè, “basic blocks” di codice sequenziale più lunghi) Vantaggi: La predizione “Always-not-taken” funziona meglio (branch più rari) Il compilatore ha più possibilità per ottimizzare il codice (branch più rari, blocchi più lunghi, più opportunità per il riordinamento delle istruzioni, risolvendo così ad esempio data hazards) Codice potenzialmente più corto e/o più efficiente Svantaggi: Svolgimento di lavoro inutile: fetch ed esecuzione di alcune istruzioni che poi non hanno effetto (negativo soprattutto per branch facili da predirre) Occorre fare il fetch sia del percorso “taken” sia di quello “untaken” Codice potenzialmente più lungo Necessario il supporto nell’ISA In questo modo, è possibile eliminare tutti i branch? No, i “loop (backward) branch” non possono essere eliminate, ma solo i “forward branch”. Permette di eliminare solo i forward branches, non i backward brances (dunque, non funziona per i loop, perché ad un certo punto bisogna decidere se saltare indietro oppure no.

Control Dependencies E’ essenziale mantenere la pipeline piena con la corretta sequenza delle istruzioni “dinamiche” (a tempo di esecuzione). Soluzioni potenziali per gestire salti condizionali (che sono delle control-flow instructions): Stallare la pipeline finchè il branch non è risolto Predirre il prossimo fetch address (branch prediction) Branch ritardati (branch delay slot) Fare qualcos’altro (fine-grained multithreading) Eliminare le istruzioni di controllo (predicated execution) Fare il fetch da entrambi i percorsi possibili (multipath execution), se i relative indirizzi sono noti

Delayed Branching Cambiamento della semantica di una istruzione di branch: Esegui il Branch dopo N istruzioni, oppure Esegui il Branch dopo N cicli di clock Idea: Ritardare l’esecuzione di un branch. Le N istruzioni (delay slots) che seguono un branch sono SEMPRE eseguite a prescindere dalla direzione (taken/not-taken) del branch. Problema: Come trovare istruzioni per riempire il(i) delay slot(s)? Regola: la condizione dei branch deve essere indipendente dalle istruzioni inserite nel delay slot Nota bene: per I salti incondizionati è molto più facile riempire i delay slots

Delayed Branching Esempio. Assumiamo una pipeline a due stadi (fetch, execute): Normal code: Timeline: Delayed branch code: Timeline: A A if ex if ex B C C A BC X A B A B C A BC X C B D BC C D E BC C E B BC F -- BC F G B X: G G -- X: G 6 cicli 5 cicli

Come Riempire il Delay Slot Se l’istruzione precedente non può essere schedulata, perché la condizione del branch dipende dal suo risultato: Il delay slot viene schedulato con una istruzione indipendente prima del branch. Se possibile, è il caso ideale, perché fornisce sempre speed-up Lo slot è schedulato dal target del branch. Si assume che il branch sia TAKEN con elevata probabilità. Altrimenti nel fall-through path devi compensare la sub con una add (fixup code) Lo slot è schedulato con una istruzione dal cammino di branch UNTAKEN. Se il branch è taken, occorre annullare l’effetto della sub con una add (fixup code) L’istruzione del delay slot è eseguita in ogni caso. Se la predizione è sbagliata, il lavoro fatto è stato inutile, ma va garantito il corretto funzionamento del programma attraverso operazioni di annullamento degli effetti.

Delayed Branching Vantaggi: + Tiene la pipeline piena con istruzioni utili ed in modo semplice, assumendo: 1. Numero di delay slots == numero di istruzioni per mantenere la pipeline piena finchè il branch non è risolto 2. Tutti i delay slot possono essere riempiti con istruzioni utili (!) Svantaggi: -- Non è facile riempire i delay slots (neppure con pipeline a 2 stadi!) 1. il numero di delay slot aumenta con la profondità della pipeline, e con il numero di istruzioni contemporaneamente in fetch (per architetture avanzate) -- vincola strettamente l’ISA alla microarchitettura 1. SPARC, MIPS, HP-PA: 1 delay slot cosa succede se cambia la profondità della pipeline nella prossima generazione dell’architettura?

Control Dependencies E’ essenziale mantenere la pipeline piena con la corretta sequenza delle istruzioni “dinamiche” (a tempo di esecuzione). Soluzioni potenziali per gestire salti condizionali (che sono delle control-flow instructions): Stallare la pipeline finchè il branch non è risolto Predirre il prossimo fetch address (branch prediction) Branch ritardati (branch delay slot) Fare qualcos’altro (fine-grained multithreading) Eliminare le istruzioni di controllo (predicated execution) Fare il fetch da entrambi i percorsi possibili (multipath execution), se i relative indirizzi sono noti

Multithreading a Grana Fine Idea: l’hardware ha contesti multipli (o hardware threads). Ad ogni ciclo, il motore di fetch fa il fetch da diversi contesti. Per tutto il tempo che ci mette un branch a risolversi, non c’è alcuna necessità di fare il fetch di un’altra istruzione dello stesso thread La latenza per la risoluzione del branch è sovrapposta con l’esecuzione di istruzioni degli altri thread Vantaggi + nessuna logica per la gestione di hazard dati e di controllo all’interno dello stesso thread Svantaggi -- Penalizza la “Single-thread performance” -- Hardware addizionale per memorizzare il “context” (es., un RegFile ed un PC per ogni thread) -- Se non ci sono thread a sufficienza, non si riesce a mascherare la latenza di un thread, e non aumenta neppure il throughput Stadi di pipeline

Multithreading a Grana Fine Idea: Commutare su un’altro thread hardware ad ogni ciclo in modo che nella pipeline non ci possano essere contemporaneamente due istruzioni dello stesso thread. Alle istruzioni di un thread, la macchina appare «non-pipelined» Trade-off system throughput vs. single-thread performance E’ la tecnica di riferimento per le GPU Ogni thread dedicato al processing di una porzione diversa di un’immagine

Multithreaded Pipeline Slide from Joel Emer Obiettivo: evidenziare il costo di tenere contesti multipli in hardware L’architettura SUN Niagara ha una pipeline multi-threaded Lettura Consigliata: Kongetira et al., “Niagara: A 32-Way Multithreaded Sparc Processor,” IEEE Micro 2005.

Control Dependencies E’ essenziale mantenere la pipeline piena con la corretta sequenza delle istruzioni “dinamiche” (a tempo di esecuzione). Soluzioni potenziali per gestire salti condizionali (che sono delle control-flow instructions): Stallare la pipeline finchè il branch non è risolto Predirre il prossimo fetch address (branch prediction) Branch ritardati (branch delay slot) Fare qualcos’altro (fine-grained multithreading) Eliminare le istruzioni di controllo (predicated execution) Fare il fetch da entrambi i percorsi possibili (multipath execution), se i relative indirizzi sono noti

Branch Prediction: Predizione della Prossima Istruzione per il Fetch PC 0x0004 0x0008 0x0007 0x0005 0x0006 ?? Assumiamo che il branch sia risolto nello stadio di WB I-Mem DEC RF WB 0x0001 LD R1, MEM[R0] D-mem 0x0002 ADD R2, R2, #1 Senza branch prediction 0x0003 BRZERO 0x0001 12 cicli al decode della LD 0x0004 ADD R3, R2, #1 0x0005 MUL R1, R2, R3 0x0006 LD R2, MEM[R2] Branch prediction 0x0007 LD R0, MEM[R2] 8 cicli

Misprediction Penalty PC Flush!! I-$ DEC RF WB 0x0001 LD R1, MEM[R0] 0x0007 0x0006 0x0005 0x0004 0x0003 0x0002 D-$ ADD R2, R2, #1 0x0003 BRZERO 0x0001 0x0004 ADD R3, R2, #1 0x0005 MUL R1, R2, R3 0x0006 LD R2, MEM[R2] 0x0007 LD R0, MEM[R2] Occorre invalidare le istruzioni negli stadi di fetch, decode, RF ed EXE (FLUSH DELLA PIPELINE)

Branch Prediction Che cosa esattamente devo predirre? Devo predirre che l’istruzione in fetch è un Branch Devo predirre la direzione del branch (taken, untaken) Devo eventualmente predirre il branch target address A Branch condition, TARGET Sono tutte le domande cui devo rispondere per poter fare il fetch della prossima istruzione dopo A: B1 B3 Pipeline Fetch Decode Rename Schedule RegisterRead Execute D B1 A B1 A D E F B1 A D E F B3 B1 A D E F B1 A D E F B1 A D E F B1 A D E F B1 A D B1 A D E F B1 A D E A B1 A D E F B1 A D E F E What to fetch next? Target Misprediction Detected! Flush the pipeline Fetch from the correct target Verify the Prediction F

Flush della Pipeline Rappresenta l’invalidazione di tutti gli stadi di pipeline che precedono la risoluzione di un branch Esempio. Assunzioni: 1- Branch risolto nel secondo stadio 2 – Misprediction penalty: 1 ciclo (cioè, 1 istruzione da invalidare)

Flush della Pipeline Rappresenta l’invalidazione di tutti gli stadi di pipeline che precedono la risoluzione di un branch Branch in corso di risoluzione: Misprediction! Istruzione predetta (in modo sbagliato)

Flush della Pipeline Rappresenta l’invalidazione di tutti gli stadi di pipeline che precedono la risoluzione di un branch Branch risolto che procede lungo la pipeline Segnali di controllo a zero per propagare la bolla Branch target corretto in fase di fetch Segnale di Flush con cui viene resettato il registro IF/ID Stadio in cui l’istruzione viene invalidata

Branch Prediction: Come si Implementa? Osservazione: assumendo che la direzione sia correttamente predetta (problema che per ora lasciamo aperto), Il Target address di un salto condizionale rimane lo stesso attraverso le invocazioni successive a tempo di esecuzione Target Address = PC+4+offset Perchè non memorizzarlo? Idea: Memorizzare il target address del branch la prima volta che viene eseguito, ed accedervi le volte successive tramite il PC se si predice “Taken” Questa memoria di appoggio prende il nome di “Branch Target Buffer (BTB)” o “Branch Target Address Cache”

Fetch Stage con BTB e Predizione della Direzione Direction predictor taken? PC + 4 Next Fetch Address Program Counter hit? Branch Target Buffer Indirizzo della Istruzione corrente (è un branch, ma la microarchitettura non può saperlo) Il Fetch mi fornisce sia l’istruzione da eseguire sia il prossimo valore del PC target address Instruction Memory Instruction fetch (branch)

Fetch Stage con BTB e Predizione della Direzione Direction predictor taken? PC + 4 Next Fetch Address Program Counter hit? Branch Target Buffer Indirizzo della Istruzione corrente (è un branch, ma la microarchitettura non può saperlo) Ma come faccio a sapere se è un branch? Un HIT nel BTB significa predirre che è un branch! target address Instruction Memory Instruction fetch (branch)

Fetch Stage con BTB e Predizione della Direzione Direction predictor La predizione «always untaken» non necessita di BTB, perché Next_PC=PC+4 taken? PC + 4 Next Fetch Address Program Counter hit? Branch Target Buffer Indirizzo della Istruzione corrente (è un branch, ma la microarchitettura non può saperlo) Grazie al BTB posso implementare ance una predizione «always taken», poiché ho il branch target address! target address Instruction Memory Instruction fetch (branch)

Performance Analysis La predizione della direzione determina la performance Ipotesi: predizione “always untaken” (cioè, PC_next=PC+4) Predizione corretta  nessuna penalty ~86% delle volte Predizione incorretta  2 stalli (dipende da dove il branch è risolto) Assumiamo: no data hazards 20% di salti condizionali 70% di queste istruzioni sono “taken” Clocks-per-instruction (CPI) = [ 1 + (0.20*0.7) * 2 ] = = [ 1 + 0.14 * 2 ] = 1.28 Penalità per misprediction Probabilità di misprediction Possiamo ridurre questi due parametri?

Riduzione della Penalty Risolviamo la condizione e il target address prima possibile, ad esempio già nella fase di DECODE Calcolo del Branch Target Address Aggiungo logica di confronto in DECODE Aumenta la lunghezza del timing path in DECODE (cala la frequenza di clock?) Seleziono il prossimo PC sulla base della condizione risolta

Riduzione della Penalty Risolviamo la condizione e il target address prima possibile, ad esempio già nella fase di DECODE Un registro usato nella condizione potrebbe essere in viaggio lungo la pipeline (non ancora aggiornato). La forwarding unit lo deve inoltrare alla logica di confronto. Si complica la forwarding logic

Riduzione della Penalty Risolviamo la condizione e il target address prima possibile, ad esempio già nella fase di DECODE Il branch ha bisogno in DECODE di un registro che deve ancora essere «calcolato» da EXE (es., una istruzione R precede il branch, e calcola un registro che serve al branch per verificare la condizione). Occorre stallare la pipeline finchè EXE non calcola il registro. Si innescano più data hazards. Si complica la detection unit.

Branch Prediction Statica Come posso invece intervenire sulla probabilità di misprediction? Predizioni del tipo “Always Taken” oppure “Always Untaken” sono esempi di predizioni statiche = la predizione è fissa per ogni esecuzione dello stesso branch (es., anche con dati diversi o con una «storia» diversa con cui si è arrivati al branch in esame) Tecniche “Compile time (statiche)” Always not taken (non necessita di BTB) Always taken (in generale porta a meno CPI) BTFN (Backward taken, forward not taken) Profile based (likely direction) Program analysis based (likely direction) Direction predictor taken? La predizione statica può essere realizzata in hardware, oppure «suggerita» dal compilatore

Suggerimento «Statico» del Compilatore Il compilatore potrebbe dare suggerimenti all’hardware! Richiede che le istruzioni di branch abbiano un bit riservato che possa essere modificato dal compilatore Bit a «1» se ritiene probabile l’esito Taken Bit a «0» altrimenti Il compilatore può decidere il valore del bit in base al tipo di istruzione oppure al profiling del programma Esempio di predizione statica BTFN: Si calcola OFFSET = BTA – PC a tempo di compilazione Se OFFSET > 0, si predice Not-Taken Se OFFSET <0, si predice Taken (probabilmente è un ciclo)

Branch Prediction Dinamica Come posso invece intervenire sulla probabilità di misprediction? Accoppiate con predizioni del compilatore, le predizioni statiche possono raggiungere buona performance per pipeline corte. Ma per pipeline più lunghe, dove la misprediction penalty è maggiore? Meglio tentare predizioni dinamiche. Idea: Predirre la direzione dei branch sulla base di informazioni dinamiche (raccolte a tempo di esecuzione)

Branch Prediction Dinamica Vantaggi + Predizione basata sulla storia dell’esecuzione dei branch e/o sul suo “contesto” + Capacità di adattamento della politica di predizione a variazioni dinamiche del comportamento dei branch. + Non serve alcun profiling statico del codice Svantaggi -- Maggior complessità (è richiesto hardware aggiuntivo) Tecniche “Run time (dinamiche)” Last time prediction (single-bit) Two-bit counter based prediction Two-level prediction (global vs. local) Hybrid Direction predictor taken?

Last Time Predictor Last time predictor Richiede un singolo bit per ogni branch Indica in che direzione è andato il branch in esame l’ultima volta che è stato risolto Esempio di storia di un branch: TTTTTTTTTTNNNNNNNNNN  90% accuratezza di predizione predict taken not actually not taken Macchina a stati di un Last Time Predictor

Last Time Prediction Aggiornamento del branch prediction buffer: 1-bit Branch Prediction Buffer oppure estensione del BTB Branch Target Buffer Ultima volta? 0x00FA83 TAKEN Predico che sarà TAKEN anche questa volta Inverti il bit nel buffer E’ TAKEN? Flush delle istruzioni caricate NO SI Tutto come previsto!

Problema del Last Time Predictor Consideriamo un loop: Prediction: TAKEN! 1 MISPREDICTION quando esco dal loop: Predico «taken» invece è «untaken» 1 MISPREDICTION quando entro nel loop: Predico «untaken» invece è «taken» 2 MISPREDICTION PER OGNI LOOP, MA SOLO UNA E’ INEVITABILE Problema: sbaglia sempre due volte la predizione di un loop branch (alla prima iterazione e all’ultima iterazione). Accuratezza per un loop con N iterazioni = (N-2)/N + OK con loop branches dove il loop ha un gran numero di iterazioni -- Non va bene quando il numero di iterazioni è ridotto: TNTNTNTNTNTNTNTNTNTN  0% accuratezza di predizione COME SI PUO’ RISOLVERE? GUARDANDO ALLA STORIA, NON SOLO ALL’ULTIMA VOLTA, OPPURE NON ESSENDO COSI’ PRECIPITOSI NEL CAMBIARE STRATEGIA.

Miglioramento del Last Time Predictor Problema: Il “last-time predictor” cambia la sua predizione da TNT o da NTT troppo velocemente e questo nonostante il branch possa essere “per lo più” taken o not taken. Idea: Aggiungere “isteresi” al predittore in modo che la predizione non cambi alla prima misprediction. Utilizzare 2 bit per tenere conto della storia delle predizioni per un branch anzichè un singolo bit Questo predittore ha due stati la cui uscita è Taken, e due stati la cui uscita è Untaken. Lettura per gli appassionati: Smith, “A Study of Branch Prediction Strategies,” ISCA 1981.

Nuovo Predittore: Contatore a 2-bit Questa FSM inverte la predizione dopo due mispredictions consecutive Ci vogliono due bit per codificare 4 stati, da associare ad ogni branch. I due bit sono di solito memorizzati nel/gestiti dal BTB. Ogni entry del BTB ha i 2-bi associati per la predizione di direzione. Strongly taken: 00 Weakly taken: 01 Codifica degli stati: Strongly !taken: 11 Weakly !taken: 10

Predizione Basata su 2-Bit Counter Ogni branch è associate con un contatore a 2-bit Il bit in più fornisce l’isteresi cercata: la predizione non cambia più troppo velocemente. Vantaggi + Miglior accuratezza di predizione Accuratezza per un loop branch con N iterazioni = (N-1)/N TNTNTNTNTNTNTNTNTNTN  50% accuratezza (contatore inizializzato a “weakly taken”) Svantaggi -- Aumenta il costo hardware

E’ sufficiente? ~85-90% accuratezza per molti programmi mediante la predizione con contatore a 2-bit (predizione bimodale) E’ possibile fare ancora meglio….

Limiti della Predizione Dinamica a 2-bit Le predizioni sono effettuate con la storia di un singolo branch C’è influenza dagli altri branch? Sperimentalmente, si trova che un dato branch è influenzato dai branch che sono stati eseguiti di recente (global branch correlation) Ia correlazione del branch con sé stesso non è considerata Il contatore a 2-bit guarda solo ad una piccola parte della storia recente di un branch: «l’ultima volta»! Sperimentalmente, si trova che un dato branch è influenzato dalla sua storia (local branch correlation) In generale, si ottengono predizioni più accurate correlando la predizione di un branch al contesto (correlating predictors) Un branch può avere un pattern di 5 stadi.

Global Branch Prediction a 2 Livelli I Livello: Global branch history register (N bits) Registra la direzione presa dagli ultimi N branch eseguiti II Livello: Tabella di 2-bit counters per ogni possibile pattern della storia globale Indica la direzione presa dal branch l’ultima volta che la stessa storia è stata vista Pattern History Table (PHT) -recente +recente 00 …. 00 1 1 ….. 1 0 00 …. 01 2 3 GHR (global history register) 00 …. 10 index A me sembra ci sia un PHT per ogni branch. E’ roba in più memorizzata nel BTB. Secondo me, ogni entry del BTB ha tutta la PHT Oppure si deve assumere che si sia solo un PHT e che tutti i branch debbano avere lo stesso comportamento data una certa storia globale. 1 Yeh and Patt, “Two-Level Adaptive Training Branch Prediction,” MICRO 1991. 11 …. 11 Quando il branch viene risolto, si aggiorna sia il GHR sia l’entry nella PHT

Two-Level Global History Predictor Direzione dei precedenti Branch (globali) Tabella di 2-bit counters taken? Global branch history PC + inst size Next Fetch Address Program Counter hit? Indirizzo dell’istruzione corrente target address BTB: Branch Target Buffer

Miglioramento dei Predittori Globali Idea: non è vero che tutti i branch, quando vedono la stessa storia globale, si comportano allo stesso modo. Soluzione: bisogna considerare anche il PC, non solo il GHR, per la predizione. Gshare Predictor: GHR “hashed” (combinato) con il PC del Branch + Più informazioni di contesto + Miglior utilizzo della PHT -- Aumenta la latenza di predizione McFarling, “Combining Branch Predictors,” DEC WRL Tech Report, 1993. Direction Prediction PC GHR

Two-Level Gshare Predictor Direzione dei precedenti Branch (globali) Tabella di 2-bit counters taken? Global branch history PC + inst size Next Fetch Address XOR Program Counter hit? Indirizzo dell’istruzione corrente target address BTB: Branch Target Buffer

Local Branch Prediction a 2 Livelli I Livello: Un insieme di Local History Registers (ciascuno ad N bit) che riportano la storia di ogni branch. La selezione del registro giusto avviene sulla base del PC del branch II Livello: Tabella di 2-bit counters per ogni configurazione del Local History Register Indica la direzione del branch l’ultima volta che esso ha avuto una certa storia Pattern History Table (PHT) 00 …. 00 PC 1 1 ….. 1 0 00 …. 01 2 3 00 …. 10 index 1 Local history registers 11 …. 11 Yeh and Patt, “Two-Level Adaptive Training Branch Prediction,” MICRO 1991.

Two-Level Local History Predictor Direzione presa dalle precedenti esecuzioni di *un dato* branch 2-bit counters taken? PC + inst size Next Fetch Address Program Counter hit? Indirizzo dell’istruzione corrente target address BTB: Branch Target Buffer

Hybrid Branch Predictors Idea: Utilizzare più di un tipo di predittore (come fossero algoritmi di predizione diversi) e selezionare la miglior predizione a tempo di esecuzione Es., predittore ibrido con 2-bit counters (1 livello), local predictor (2 livelli) e global predictor (2 livelli) Vantaggi: + Miglior accuratezza: diversi predittori sono migliori per branch diversi + Riduzione del tempo di warmup (predittori con warmup veloce sono usati fintantochè quelli più lenti non hanno raggiunto il warmup) Svantaggi: -- Necessitano di metodologie di scelta tra i predittori (selettori) -- Aumenta la latenza di predizione Riferimento: McFarling, “Combining Branch Predictors,” DEC WRL Tech Report, 1993. Last time predictor gli basta l’ultima volta; warmup veloce Predittori a 2 livelli hanno bisogno di incontrare un branch diverse volte

Alpha 21264 “Tournament Predictor” Branch penalty tipica: 11+ cicli Soluzione ibrida: predittore globale + predittore locale Predittore Locale a 2 livelli con contatori a 3 bit (maggiore isteresi) Predittore Globale a 2 livelli con contatore a 2 bit La scelta avviene sulla base della storia globale: “l’ultima volta che ho visto questa storia globale, il predittore più accurate è risultato quello locale/globale” Riferimento: Kessler, “The Alpha 21264 Microprocessor,” IEEE Micro 1999. PHT PHT Questo predittore ci metto tanto a fare il warmup. Allora ci potrebbe essere un bimodale intanto che non

Idea Finale I processori avanzati hanno diversi predittori dinamici, e di volta in volta devono scegliere quello ritenuto più affidabile per il branch di cui stanno facendo il fetch. Il warmup dei predittori è un fattore importante di questa scelta.