di  -  lunedì 3 gennaio 2011

Dopo una lunghissima pausa (causa tapeout) eccoci di nuovo qui con una nuova puntata sull’architettura dei processori superscalari. Nell’ultimo articolo abbiamo dato uno sguardo a cosa significa fare il fetch delle istruzioni in un processore superscalare e a quali sono i principali limiti. Abbiamo visto come il disallineamento del gruppo di fetch rispetto alla cache line riduca la banda effettiva verso la memoria, e alcune strategie per combattere questo problema.

Rimane ora investigare l’altro grande problema, uno dei singoli problemi peggiori dal punto di vista delle prestazioni: le modifiche al flusso del programma causate dai branch (salti condizionati). In questo articolo vedremo perchè la branch prediction è una parte fondamentale di un processore superscalare, quali sono i componenti chiave e come questi si innestano nell’architettura del processore. Nei prossimi articoli descriverò alcune delle tecniche usate per costruire i predittori.

Per capire meglio perchè i branch sono così deleteri per le macchine superscalari, consideriamo questo “problema giocattolo” (cioè estremamente semplificato per mettere in evidenza questo specifico problema e permettere di ottenere informazioni utili con pochi calcoli a mente):

  • consideriamo un programma con un branch ogni 5 istruzioni (20% branch)
  • non consideriamo data hazardsstructural hazards
  • non consideriamo i cache miss
  • confrontiamo una pipeline a 5 stadi base (rigida, scalare, senza forwarding paths) con quello di un ipotetico processore superscalare con 5 pipeline parallele (supponiamo che tutti gli stadi abbiano larghezza 5) e con 20 stadi
  • supponiamo che i branch vengano risolti nello stadio di esecuzione, il terzo su 5 nella pipeline semplice e il dodicesimo su 20 nella pipeline superscalare (per simmetria: 3:5 = 12:20)

La pipeline semplice si comporterà così:

dove il tempo scorre in orizzontale, e per ogni colpo di clock viene mostrato lo stato della pipeline. Le prime 5 istruzioni riempiono l’intera pipeline in 5 colpi di clock, poi due bolle devono essere inserite mentre si aspetta che il branch arrivi all’unità di esecuzione; a questo punto il Program Counter viene settato al valore corretto, e il programma può riprendere. In totale servono 7 cicli di clock (5 utili più 2 bolle) per eseguire il blocco di 5 istruzioni, cioè un IPC (Instruction Per Clock) di 5 / 7 = .71

La pipeline superscalare invece si comporterà così:

dove ho mostrato solo il primo e il tredicesimo colpo di clock. Ora tutte e 5 le istruzioni possono viaggiare in parallelo, perchè stiamo trascurando tutti gli hazard che non siano quelli di controllo. Purtroppo, mentre questo “pacchetto” di istruzioni si fa strada nella pipeline, sta lasciando il deserto dietro di sè: la pipeline è completamente vuota perchè il processore non sa qual è la prossima istruzione. Al dodicesimo ciclo di clock, con 11 stadi vuoti dietro di sè, il processore risolve finalmente il branch, reindirizza il Program Counter, e cinque nuove istruzioni vengono caricate. La media è 5 istruzioni in 12 cicli di clock, cioè un IPC di 5 / 12 = .42

In questo schema idealizzato il processore superscalare ha prestazioni molto più basse della pipeline semplice, ma usa molta più area, più potenza, ed è molto più difficile da progettare: un fallimento su tutta la linea! Addirittura, l’IPC è .42 mentre il picco teorico sarebbe 5 (5 istruzioni parallele per ciclo di clock), cioè più del 90% delle prestazioni potenziali viene buttato via. Intuitivamente, il problema è che un processore superscalare ha molti più “slot” (istruzioni eseguibili) che rimangono vuoti per ciclo di clock, oltre ad avere in media più stadi di pipeline.

Il risultato di questo problema è che non possiamo assolutamente pensare di costruire un processore superscalare senza risolvere questo problema all’apparenza insolubile: eseguire istruzioni che seguono un branch prima ancora di averlo valutato (anzi, prima ancora di averlo decodificato!).

Branch prediction

La soluzione a questo problema è, almeno di nome, nota a tutti (almeno su queste pagine :) e si chiama branch prediction. E’ uno degli esempi più importanti dell’uso delle tecniche di speculazione all’interno del processore: speculiamo che il flusso del programma segua un certo percorso, e facciamo il fetch delle istruzioni da quel percorso prima di sapere se è quello giusto. Ovviamente, la speculazione non deve avere alcun impatto sulla correttezza del programma, ma solo sulle prestazioni. I branch vanno comunque eseguiti, e il loro risultato confrontato con la nostra ipotesi: se avevamo speculato correttamente il branch diventa una NOP dal punto di vista del programma (era già stato eseguito correttamente); altrimenti, è necessario tornare sui propri passi, cancellare tutte le istruzioni eseguite speculativamente, ritornare al Program Counter del salto, e riprendere ad eseguire il programma dal percorso giusto (misprediction recovery).

Il meccanismo può funzionare solo se i branch hanno un comportamento ripetitivo e quindi prevedibile. Fortunatamente è così per la maggior parte dei programmi, il che permette ai migliori predittori disponibili oggi di “indovinare” la direzione di salto con precisione incredibile (anche superiore al 98% in certi casi).

Prima di vedere le tecniche che sono state sviluppate per risolvere il problema è necessario osservare più da vicino da dove vengono i branch e quali strutture hardware sono necessarie alla loro esecuzione.

Grafi di flusso e branch

Un programma può essere decomposto in basic blocks, cioè blocchi di istruzioni che vengono eseguite una dopo l’altra senza interferenze da parte di branch o jump. I programmi possono essere descritti come grafi orientati dove i basic block sono i nodi, e gli archi definiscono le possibili transizioni tra i blocchi. Quando da un blocco si può uscire in due diverse direzioni (cioè da esso partono due archi) siamo in presenza di un branch, altrimenti si tratta di un jump, ad esempio:

In questo caso verrà eseguito il blocco A, quindi B o C, e infine D; B è un loop e può essere eseguito una o più volte. Per disporre il programma in memoria è necessario “linearizzare” le istruzioni, cioè disporre i blocchi uno dopo l’altro sequenzialmente in un qualche ordine, ad esempio:

I branch diventeranno quindi un’alternativa tra eseguire l’istruzione immediatamente seguente, oppure saltare ad un diverso indirizzo di memoria e riprendere da lì. L’esecuzione di un salto è quindi composta da due fasi: la risoluzione della condizione (cioè se effettuare il salto o meno) e la generazione del bersaglio di salto. La risoluzione della condizione può avvenire in diversi punti della pipeline, ad esempio:

Nel caso il salto dipenda da dei registri Condition Code (come ad esempio i flag bits) allora la condizione potrebbe essere risolta indicativamente intorno allo stadio di Dispatch. Se invece è richiesto un calcolo sui registri, allora sarà necessario eseguire questo calcolo nello stadio di esecuzione.

Anche la generazione del bersaglio può avvenire in punti diversi della pipeline, ad esempio:

Se il salto è relativo al Program Counter attuale possiamo calcolare il bersaglio subito dopo la decodifica dell’istruzione di branch, sommando il campo Immediate (per usare la terminologia MIPS) al PC corrente. Se il salto è invece al valore contenuto in un registro possiamo avere il besaglio appena il valore è disponibile, di nuovo intorno allo stadio di Dispatch. Se invece il bersaglio va calcolato con un’operazione più complessa, dovremo aspettare di eseguire il branch nella sua pipeline dedicata.

Branch predictor come black-box, predizioni multiple e misprediction recovery

Ciò detto, e considerando che non vogliamo aspettare la risoluzione dei salti per procedere col programma, che faccia dovrebbe avere il nostro predittore? Dovrà essere qualcosa del genere:

Ad ogni ciclo di clock abbiamo bisogno di sapere dove prelevare le istruzioni; da questo punto di vista il predittore non è altro che una funzione che fornisce il prossimo Program Counter a partire da quello corrente.

In termini di teoria del controllo, questa funzione è in sostanza un sistema feed-forward: il predittore fornisce il prossimo PC senza sapere come il PC corrente eseguirà, ma solo in funzione di come ha eseguito nel passato, cioè dello stato corrente del predittore. Questo permette al predittore di essere molto veloce, ma non completamente accurato. Il predittore stesso, invece, è inserito in un anello di feedback: una volta che il risultato del salto è noto, il predittore viene aggiornato con le ultime informazioni, in modo da essere più accurato la prossima volta. Naturalmente il risultato del salto viene anche usato per la misprediction recovery, se la predizione si rivelasse errata.

Questo è sempre il caso nelle tecniche che usano speculazione: richiedono un meccanismo di validazione della speculazione, per garantire il corretto funzionamento del sistema. Le due componenti descritte prima possono quindi anche essere viste come due meccanismi interagenti: il primo meccanismo, nel front-end della pipeline, fornisce la speculazione, mentre il secondo, nel back-end, effettua la validazione e la correzione degli errori.

A questo punto una domanda potrebbe sorgere spontanea: cosa succede se, mentre stiamo eseguendo delle istruzioni in modo speculativo a seguito di un salto, incontriamo un altro salto? La risposta è ovvia: effettuiamo un’altra speculazione. È possibile avere più salti in attesa di risoluzione e istruzioni in esecuzione speculativa che dipendono da uno o più di questi salti. Per fare ordine, e recuperare dai possibili errori, serve mantenere:

  • una tag diversa per ogni blocco base: ogni istruzione in esecuzione speculativa sarà accompagnata dalla tag del blocco base a cui appartiene
  • un buffer con gli indirizzi dei salti che stiamo eseguendo speculativamente: è necessario nel caso una predizione di salto si rivelasse errata e si fosse costretti a tornare indietro e ripetere il salto nella direzione giusta

Per fare un esempio, supponiamo di aver incontrato 3 salti e di star eseguendo speculativamente le istruzioni che li seguono; supponiamo anche che il predittore abbia generato le predizioni {preso, non-preso, preso} per i 3 salti. Lo stato delle istruzioni nella pipeline può essere schematizzato così:

dove ho indicato in verde i blocchi validati (cioè corretti), in blu i blocchi in esecuzione speculativa, in rosso i blocchi speculati erroneamente, e in grigio i blocchi scartati dal predittore. Ad ogni rombo corrisponde un salto. Le istruzioni prima del primo salto non stanno eseguendo speculativamente, e quindi non hanno alcuna tag. Le istruzioni che seguono il primo salto (o meglio, che seguono l’indirizzo bersaglio del primo salto, visto che la predizione è “salto preso”) hanno la tag 1, e così via.

Ad un certo punto i branch eseguono e il meccanismo di validazione entra in scena. Supponiamo che il primo salto fosse stato predetto correttamente, mentre il secondo no; la situazione ora sarà:

Tutte le istruzioni con la tag 1 sono state validate, e la loro tag rimossa: non stanno più eseguendo speculativamente, e potranno aggionare i registri architetturali quando saranno ritirate. Tutte le istruzioni con la tag 2 devono essere cancellate, e così quelle con tag 3, visto che dipendono dal secondo salto (errato).

Un modo per farlo sarebbe quello di andare a controllare tutti gli stadi di tutte le pipeline, il reorder buffer, le reservation station, eccetera, cercare le tag incriminate, e cancellare le istruzioni errate. Questo può essere costoso in termini di logica necessaria.

Un altro modo può essere quello di sfruttare un meccanismo già esistente per la gestione precisa delle eccezioni (rapidamente accennata qui). In pratica, è sufficiente fare così:

  • andare nel reorder buffer, che contiene una entry per ogni istruzione in-flight nel processore
  • cercare le tag incriminate e marcare quelle istruzioni come “invalide”

Le istruzioni errate continueranno ad eseguire, attraversando le pipeline e arrivando al reorder buffer. Al momento del ritiro, serve un pezzo di logica che controlli il bit di validità: le istruzioni invalide verranno semplicemente scartate senza avere alcun impatto sui registri architetturali. Dal punto di vista del software quelle istruzioni non sono mai state eseguite. Questo permette di semplificare molto la logica di recovery, al prezzo di eseguire istruzioni che sappiamo essere errate (spreco di energia).

Conclusioni e… quiz!

In questa prima parte abbiamo visto perchè la predizione dei salti è assolutamente necessaria per garantire buone prestazioni ai processori superscalari, e abbiamo anche visto come i pezzi chiave del meccanismo di predizione si innestano nell’architettura del processore. Nei prossimi articoli descriverò alcune delle tecniche usate per generare le predizioni, con esempi tratti da processori commerciali.

E ora, per i pochi coraggiosi che sono riusciti ad arrivare fino alla fine… quiz! Consideriamo un pezzetto di codice molto semplice e comune, come un if che protegge la dereferenziazione di un puntatore:

int a;
int* p = NULL;
...
if(p != NULL) a = *p

Quiz: in quale problema potrebbe incorrere questo innocentissimo codice, una volta che al nostro processore aggiungiamo la branch prediction?

21 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    thebol
     scrive: 

    se la prima esecuzione l’if è a true e la seconda e a false, nella seconda esecuzione si ha un null pointer, perché il branch viene preso in base all’esecuzione precedente.

    Immagino che l’eccezione emessa durante una branch prediction vada stoppata fino a quando il branch viene validato o no.

  • # 2
    Xeus32
     scrive: 

    Ottimo articolo come sempre!
    Un giorno puoi fare uno speciale sulle MMU in particolare sulle tecnologie di Hypervision e Protected Mode.
    In particolare sono molto incuriosito da queste tecniche per l’isolamento del codice su vari Ring!

    Ma forse è fuori argomento!

  • # 3
    Cesare Di Mauro
     scrive: 

    Penso che un problema potrebbe sorgere quando il salto viene predetto come “not taken”, e quindi il codice prosegue, mentre in realtà p è NULL per cui verrebbe referenziata la locazione di memoria 0 (usualmente) ed eseguito un load.
    Soltanto dopo la CPU si accorge che la condizione è sbagliata (e quindi il salto dev’essere eseguito), e quindi non doveva effettuare il load.

    Ma qui entrano in gioco le logiche di “cancellazione” dell’esecuzione dell’istruzione di load, che è vincolata all’esito finale del branch, quindi eventuali page fault dovuti all’accesso a un indirizzo non lecito verrebbero “congelati” fino a che non si deciderà per il commit o il rollback dell’istruzione.

    Spero di non aver detto bestialità. :P

  • # 4
    Pleg (Autore del post)
     scrive: 

    @ thebol, Cesare Di Mauro

    Gia’ e’ cosi’. Se per qualche motivo p e’ NULL ma il salto non viene preso (speculativamente), si generera’ un’eccezione. Questo pero’ puo’ essere risolto in modo semplice usando il solito schema per la gestione precisa delle eccezioni: l’istruzione che genera l’eccezione alzera’ la flag opportuna nella sua entry nel reorder buffer, che verra’ controllata al ritiro. Ma al ritiro il branch ha ormai eseguito, quindi sappiamo se quel branch va preso o ignorato. E va tutto a posto.

    Forse piu’ interessante e’ cosa succede se uno store viene eseguito speculativamente: in quel caso lo store NON PUO’ aggiornare la memoria fino a che il branch da cui dipende non e’ stato validato, perche’ la memoria non si comporta speculativamente :) una volta fuori dal datapath, non c’e’ piu’ ritorno. Anche in questo caso pero’ e’ sufficiente non far entrare lo store nello store buffer fino a che non e’ validato, e tutto va a posto.

  • # 5
    Pleg (Autore del post)
     scrive: 

    @ Xeus32

    Purtroppo di quello non so molto, non ho mai lavorato sulle parti che si interfacciano col sistema operativo. Ma se dovessi scoprire qualcosa, lo faro’ senz’altro :)

  • # 6
    Pleg (Autore del post)
     scrive: 

    Quando si parla del diavolo… oggi e’ stato presentato ufficialmente Sandy Bridge, e tra le principali innovazioni architetturali c’e’… un Branch Predictor nuovo di pacca!
    http://www.anandtech.com/show/3922/intels-sandy-bridge-architecture-exposed/2
    A quanto pare usano un predittore bimodale, di cui scrivero’ qualcosa nella prossima puntata.

    Questo per dire quanto siano importanti i Branch Predictor per spremere il massimo delle prestazioni, anno dopo anno continuano a venir perfezionati e si continua a fare ricerca nel campo.

  • # 7
    Cesare Di Mauro
     scrive: 

    @Pleg. Ormai sono importantissimi anche per processori come gli ARM, che hanno l’esecuzione condizionale delle istruzioni.

    Riguardo a Sandy Bridge, ho letto il link e riguardo la questione strong/weak sono d’accordo. Per esperienza sono generalmente più “strongly” taken o not taken.

    Mi sorprende, invece, che soltanto adesso si sia preso atto che i salti più frequenti sono quelli più corti, e quindi ottimizzando lo spazio della BPU in tal senso. Per me era una cosa scontata.

  • # 8
    Pleg (Autore del post)
     scrive: 

    eh si’ adesso che gli ARM vogliono fare gli sboroni e diventare CPU potenti, dovranno mettersi a fare tutto quello che Intel fa da un pezzo, dall’esecuzione fuori ordine a BP avanzata a cache multilivello a soluzioni multicore ecc.

    E’ scontata in teoria, ma come si fa a farla in pratica? Intanto bisognera’ avere una struttura ausiliaria che “specula” a sua volta se il salto e lungo o breve, per decidere quale target buffer usare :) e poi bisognera’, nel caso di target vicino, fare lo store della differenza tra l’indirizzo del target e quello del branch, per salvare un numero piccolo. Ma questo vuol dire che poi dovro’ sommarlo di nuovo quando devo generare la predizione: qual e’ il costo di un sommatore a 64 bit nel predittore, dal punto di vista temporale? Non puo’ sforarmi la durata di un ciclo di clock, perche’ devo produrre una predizione per ciclo di clock… non saprei valutare su due piedi, ma sara’ un pezzo che andra’ ottimizzato per benino.

  • # 9
    Cesare Di Mauro
     scrive: 

    Sì, è chiaro che la mia era un’opinione basata sull’osservazione del codice generato e di come viene eseguito, e non ho fatto ipotesi sul come realizzare un apparato del genere che tenga conto dei salti brevi o lunghi.

    La butto lì: magari non viene memorizzato l’offset a 8 bit, ma solo gli 8 bit bassi della “pagina” da 256 byte corrente.
    D’altra parte il predittore non lavora sugli offset, ma sempre sugli indirizzi (virtuali).
    Si potrebbero, inoltre, avere più target buffer “piccoli”, quindi aventi più pagine da 256 byte di cui conservare gli indirizzi dei salti (i più usati).

    Sono speculazioni, tutte da verificare, ma personalmente farei così.

    Riguardo agli ARM concordo. Se qualcuno pensa che rappresentino il santo Graal, si sbaglia di grosso: hanno i loro limiti, e non hanno la bacchetta magica per superarli. I problemi riguardo la scalabilità prestazionale sono comuni a tutte le architetture. ;)

  • # 10
    Pleg (Autore del post)
     scrive: 

    In effetti memorizzare solo gli ultimi N (pochi) bit ti darebbe la quasi certezza di poter “beccare” un salto breve, a costo quasi nullo: basterebbe concatenare i (64-N) bit superiori del branch cogli N bit registrati nelal tabella.

    Riguardo agli ARM… beh finalmente oggi Jensen ha annunciato Denver, quindi entro un paio d’anni vedremo quanto rendono degli ARM di “alto livello”, considerando che NVidia e ARM insieme avranno un decimo delle risorse che Intel puo’ mettere nel progettare le sue macchine… sara’ molto molto interessante!

  • # 11
    Cesare Di Mauro
     scrive: 

    Sì, l’idea è proprio quella. Tra l’altro con sistemi a 64 bit il vantaggio è ancora più elevato: ponendo che n sia 8, all’incirca al “costo” di 2 indirizzi completi inseriti nella BTC si possono memorizzare 8 indirizzi all’interno della stessa “pagina”.

    I salti, come il codice, generalmente dovrebbero abbastanza localizzati, per cui il guadagno (o risparmio) potrebbe essere notevole.

  • # 12
    Marco
     scrive: 

    “sara’ molto molto interessante!”

    Quoto.

    Negli anni ’80 ARM, pur con un transistor count da 6502, ridicolizzava i286 e MC68020 in termini di prestazioni.
    Ovviamente è estremamente improbabile che si ripeta una situazione del genere, ma credo che la proporzione di risorse in gioco sia sostanzialmente analoga.

  • # 13
    Cesare Di Mauro
     scrive: 

    Soltanto per core molto piccoli in termini di numero di transistor.

    Con l’enorme aumento dei transistor a cui abbiamo assistito negli ultimi anni (siamo alla soglia dei 2 miliardi), che il decoder ARM sia una manciata di transistor rispetto a quello di un x86, è poco importante.

    Con un Atom già siamo nell’ordine delle decine di milioni, quindi quasi un paio di ordine di grandezza, la differenza si sente.

  • # 14
    ikari
     scrive: 

    ciao a tutti!!!
    Volevo chiedere se il branch prediction è presente anche sulle gpu o solo sulle cpu…grazie

  • # 15
    ikari
     scrive: 

    ciao…
    visto che c sono volevo chiedere se queste considerazioni sn corrette…
    la Branch Prediction cerca di prevedere quale sarà l’esito di un salto condizionale, e di trovare la direzione di salto con grande precisione, evitando cosi rallentamenti.
    A seguito della branch prediction, vengono eseguite le istruzioni interessate. Se la previsone si è dimostrata corretta allora si prosegue col normale flusso di esecuzione, altrimenti, se la previsione si è dimostrata errata, dovranno essere eliminate tutte le istruzioni eseguite a seguito della Branch Prediction e riprendere l’esecuzione dal giusto flusso d’esecuzione.

    in pratica non mi è chiaro se a seguito di una branch prediction viene solo ipotizzato quale sia il risultato della branch e in seguito venga fatta la fetch delle istruzioni di interesse senza pero che queste siano eseguite o, al contrario, vengono anche eseguite tali istruzioni.
    grazie mille in anticipo per l’aiuto!!!!

  • # 16
    Pleg (Autore del post)
     scrive: 

    Le istruzioni vengono anche eseguite: gli operandi vengono prelevati e le operazioni eseguite come fossero istruzioni “normali”. Pero’, i risultati non possono aggiornare i registri architetturali (ne’ la memoria) finche’ le istruzioni non vengono validate, cioe’ si e’ certi che andavano eseguite.

    Tutto questo meccanismo e’ completamente invisibile al software: i registri architetturali (quelli visibili al software) vengono aggiornati come se le istruzioni venissero eseguite una a una, in ordine, senza speculazione; la speculazione aggiorna solo dei registri interni della macchina.

    Nel caso di errore i risultati “temporanei” contenuti in questi registri interni vengono eliminati e l’esecuzione riprende dall’istruzione giusta e dai valori contenuti nei registri architetturali (che sono, per il meccanismo detto prima, sempre giusti).

    Per quel che riguarda le GPU non saprei; penso che l’architettura corrente di AMD (VLIW) possa fare a meno di predizione di salti, semplificando l’hardware e spostando la complessita’ nel compilatore; nvidia suppongo usi qualche tipo di branch prediction negli shader (o “CUDA core”, come li vuoi chiamare), ma sono solo mie speculazioni.

  • # 17
    Cesare Di Mauro
     scrive: 

    Dalla documentazione di CUDA che ho letto si evince che nVidia utilizza l’esecuzione condizionale delle istruzioni, in maniera simile agli ARM o ai PowerPC. ;)

  • # 18
    ikari
     scrive: 

    grazie mille per l’aiuto ^^
    quindi in pratica viene fatto fetch decode ed execute delle istruzioni che ipoteticamente soddisfano la condizione.
    ma quindi, se un programma accede ai registri architetturali, che vengono aggiornati come se le istruzioni venissero eseguite una a una senza speculazione e la speculazione aggiorna solo dei registri interni della macchina, ciò vuol dire che l’esecuzione di un programma dovrà attendere che la branch prediction sia validata. o magari intanto verranno eseguite altre porzioni di codice nel frattempo???
    grazie ancora!!!

  • # 19
    Cesare Di Mauro
     scrive: 

    Possono essere eseguite altre istruzioni che non dipendono da quelle in attesa di validazione. Nel momento in cui verrà “risolto” il branch, si procederà alla loro convalida o all’annullamento.

  • # 20
    ikari
     scrive: 

    grazie mille per l’aiuto!!!

  • # 21
    Pleg (Autore del post)
     scrive: 

    Devo ancora finire questa serie sulla branch prediction, ma dopo scrivero’ qualche articolo sul “register renaming” che e’ appunto la tecnica usata per separare i registri architetturali da quelli interni (oltre che per altre cose). In soldoni, ogni volta che un’istruzione viene decodificata le vengono assegnati dei nuovi registri (appunto “rinominati”) che sono interni alla CPU e invisibili al software, e lei opera su quelli; un pezzo di logica tiene la tabella di corrispondenze tra i registri architetturali e quelli rinominati, e alla fine il risultato viene riscritto indietro sui registri architetturali. In pratica tutte le istruzioni eseguono su uno “scracth pad” e poi ci sono dei trucchi per mantenere le corrispondenze.

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.