Dopo aver passato brevemente in rassegna le tipologie di memorie presenti al’interno di un chip, questa settimana inizieremo ad entrare nel dettaglio di cosa succede quando viene data un’istruzione ad un processore. Iniziamo col dire che lo schema di un’istruzione elementare (ad esempio RISC) è del tipo: fetch, decode, execute, memory write back. In pratica, l’istruzione viene letta, interpretata, eseguita e, in fine, il suo risultato finisce in memoria.
Questo semplificando al massimo. Scendendo più nel dettaglio, scopriamo che, ad esempio, tra uno stadio e l’altro ci sono dei particolari tipi di memoria, registri, buffer o cache. Ad esempio, esiste un particolare registro detto Program Counter (PC) che contiene l’indirizzo all’istruzione successiva a quella che si sta eseguendo.
C’è, poi, un altro registro “speciale” detto Instruction Register (IR), che contiene l’istruzione in esecuzione. Un terzo registro, il Registro di Stato, contiene informazioni sullo stato corrente dell’esecuzione e segnala eventuali errori. Ci sono poi altri due registri speciali che contengono rispettivamente gli indirizzi della cella di memoria da cui leggere o scrivere un dato e il dato da leggere o scrivere.
Quando si assegna un’istruzione ad una unità di calcolo, quindi, che sia un processore o una alu, innanzitutto si deve fornire l’indirizzo della prima delle celle di memoria da cui prelevare i dati; quindi l’istruzione è caricata nel program counter da cui è inoltrata la richiesta di accesso alla ram (DMA) gestito dal memory controller e, successivamente, l’istruzione così ottenuta è inviata nell’instruction register; qui è decodificata e mandata in esecuzione.
A questo punto, i dati necessari alla sua esecuzione sono caricati nei registi della alu e inizia l’esecuzione vera e propria. Durante l’esecuzione, viene continuamente aggiornato il registro di stato che trasmette informazioni circa lo stato dell’esecuzione e permette di trarre informazioni sulla nuova istruzione da caricare nel program counter.
Appare chiara, quindi, l’importanza della velocità con cui tutte queste operazioni sono eseguite e del perchè sia così importante avere memorie con latenze molto ridotte che si interfaccino con le unità di calcolo o con i circuiti di controllo e trasmissione del chip. Il boom del numero di registri fatto segnare in questi anni è il miglior indice di questa necessità, in quanto i registri sono il tipo di memoria a più bassa latenza (il tempo d’accesso ad un registro interno varia tipicamente tra 1 e 5 cicli di clock).
Ovviamente la schematizzazione fatta è massima, in quanto il discorso è riferito ad una sola alu e ad una sola istruzione. La cosa è molto più complessa ed articolata, poichè all’interno di un chip pesantemente multithreaded, come una gpu, sono caricate svariate migliaia di istruzioni e dati che circolano contemporaneamente all’internod elle pipeline; ciò comporta che non si ha un singolo registro di tipo PC o IR, ad esempio, ma interi buffer di registri dello stesso tipo, con la possibilità, per i chip che fanno uso di controllo di flusso di tipo dinamico, di riordinare le istruzioni in base al feedback ricevuto dai registri di stato.
Il memory controller, a sua volta, gestirà in modalità Out of Order le richieste di accesso in memoria e, di conseguenza, è dotato anche lui di buffer in cui immagazzinare le suddette richieste e decidere in che ordine soddisfarle.
L?approccio sin qui descritto, però, non è l’unico. E’ stato fatto l’esempio di un’architettura che lavora su istruzioni elementari e le cui alu non hanno la possibilità di accedere direttamente alla memoria centrale. Questo approccio è tipico delle cosiddette architetture RISC (Reduced Instruction Set Computer).
Differente il caso, ad esempio, di un’architettura di tipo CISC (Complex Instruction Set Computer); quest’ultima prevede l’utilizzo di istruzioni complesse con la unità di calcolo che possono accedere direttamente alla memoria centrale per leggere e scrivere i dati. I vantaggi di questo tipo di approccio sono maggior facilità di programmazione e maggior numero di istruzioni eseguite per ciclo da ogni singola alu. Questo perchè ad ogni istruzione di tipo CISC corrispondono più istruzioni elementari di tipo RISC.
Ciò significa, come è facilmente comprensibile, che a livello circuitale un’alu di un processore CISC è enormemente più complessa di un’alu RISC. Quindi la maggior semplicità di programmazione si ripercuote in una maggior cpmplessità circuitale. Di fatto, l’approccio di tipo RISC nasce dal tentativo di semplificare le architetture dei chip attraverso un modello che prevedesse lo spezzettamento delle macro istruzioni RISC in tante istruzioni elementari.
Questo si traduceva anche nello spezzettamento delle unità preposte alle operazioni di lettura, decodifica e esecuzione delle istruzioni, in tante unità “elementari”, disposte serialmente, che svolgessero gli stessi compiti. Nasce così il concetto di pipeline. Il vantaggio di questo approccio è la minor complessità, sia a livello progettuale che realizzativo, della singola unità e la possibilità di raggiungere un duplice obiettivo: aumentare la potenza di calcolo semplicemente replicando un blocco di stadi elementari in serie o in parallelo; aumentare le frequenze di funzionamento del chip.
Questo perchè la velocità di elaborazione del chip è condizionata dal suo stadio più lento, ossia da quello che impiega più tempo a tarminare il suo task. E’ ovvio che più uno stadio è complesso, ossia più operazioni devono essere compiute per eseguire una singola istruzione, più questo farà da freno alla possibilità di incrementare la frequenza di funzionamento del chip; se si riesce a ridurne le dimensioni è possibile aumentarnela velocità di elaborazione.
Il modo per ottenere ciò è quello di scomporre la singola unità in unità sempre più piccole, fino a ridurle ad unità di tipo “elementari”. Nascono così le architetture pipelined, ossia formate da tanti blocchi che replicano gli stessi stadi, disposti in cascata. Con l’avvento del pipeline nasce anche la necessitàdi sincronizzare le operazini dei vari stadi. Proprio questo sincronismo finisce col costituire uno dei tre principalicolli i bottiglia a cui un’architettura di tipo pipelined va incontro. Vediamo un esempio pratico: supponiamo di avere una macro alu in grado di eseguire un “processo” che sia l’equivalente di 3 istruzioni elementari; per la prima e la terza sono necessari 10 ns, per la seconda 100.
Per l’esecuzione dell’intero processo saranno necessari 120 ns. Ora immaginiamo di scomporre l’alu in 3 alu più piccoile, ognune delle quali si occupi di una singola istruzione
Il primo ed il terzo stadio dovranno aspettare che il secondo completi la sua istruzione, per cui il tempo totale per terminare il processo sarà di 300 ns. Uno svantaggio notevole!!!!!!!!!!!!!!!!!!
Vediamo cosa accade se, però, i processi sono, ad esempio, 5. Nel caso della macro alu, il tempo impiegato sarà pari a 5 volte 120 ns, ovvero 600 ns. Con la pipeline, si verifica quanto riportato in figura
dove sono riportate le varie fasi dell’esecuzione per ogni songola istruzione (ad esempio FC1 sta per FETCH istruzione 1, DC per decode, ecc). Come si può vedere, l’istruzione n termina un solo ciclo dopo che è terminata l’istruzione n-1. Questo permette di ricavare la formula che fornisce il numero dei cicli macchina di un processore con un pipeline di k stadi che elabora n processi:
T(k) = k + (n – 1)
che tradotto in tempo d’esecuzione dà, nell’esempio riportato, un valore pari a [3+(5-1)]*100=700. Ovvero, con 5 porcessi il vabtaggio di un’architettura non pipelined si assottiglia. Ma si può fare di meglio. Scomponiamo lo stadio 2 in due stadi, ciascuno dei quali impiega 50 ns a portare a termine l’elaborazione.
La formula restituisce un valore pari a [4+(5-1)]*50= 400 ns. In tal modo, grazie ad un’architettura pipelined abbiamo guadagnato 200 ns. Ma si può fare ancora meglio
In tal modo, il tempo impiegato diventa [3+(5-1)]*50=350 ns.
Da questo esempio, emerge anche un’altra considerazione: che a parità di frequenza, un’architettura che presenta unità di calcolo in parallelo è più efficace di una che ha tutte le unità disposte in serie.
In effetti, avere una disposizione prevalentemente seriale delle unità, quando gli stadi diventano tanti (un caso emblematico l’architettura netburst), finisce col rivelarsi particolarmente inefficiente anche per il problema legato ai salti condizionali ed alla propagazione delle bolle. Un salto condizionale sbagliato, ad esempio,, con architettura tra i venti e i trenta stadi, può far sprecare qualche centinaio di cicli solo per le operazioni di svuotamento e riempimento del pipeline.
Terzo possibile problema delle architetture di tipo pipelined è legato alla necessità, in caso di istruzioni dipendenti, di aspettare il risultato delle elaborazioni dei precedenti stadi prima di poter procedere ai successivi calcoli.
Per ovviare a questi inconvenienti, i moderni microprocessori sono spesso dotati dicircuiti di branching dinamico molto sofisticati a cui sono affiancati anche algoritmi di static branching. Inoltre, tra uno stadio e l’altro, sono presenti registri e cache deputati a immagazzinare i risultati delle elaborazioni intermedie e a minimizzare l’impatto degli stadi più lenti sulla velocità di elaborazione della pipeline.
Le microarchitetture di cpu e gpu, di fatto, non sono riconducibili univocamente ad un preciso modello, in quanto presentano contaminazioni anche di altri modelli architetturali.
Dalle considerazioni fatte finora, emerge che le diverse architetture finora presentate differiscono per alcuni aspetti fondamentali, come ad esempio, le modalità di accesso alla memoria centrale o la tiopologia di alu e, di conseguenza, del tipo di istruzioni elaborate.
Questo ha un’inpluenza scontata sull’architettura dei chip; così vediamo che un’architettura di tipo RISC ha unità meno complesse, ma in numero assai maggiore, stadi di dimensioni ridotte, disposti in serie e/o in parallelo e un gran numero di registri e cache; il trasferimento dei dati e delle istruzioni avviene prevalentemente facendo uso dei registri e le sue unità non accedono direttamente alla ram. Un processore CISC ha unità più complesse, è più semplice da programmare, non ha necessità di avere un gran numero di registri e cache interni e ha unità che possono accedere direttamente alla ram.
Sia per le considerazioni, anche di carattere numerico, fatte in questo articolo, sia per quanto visto la scorsa settimana sulle latenze dei vari tipi di ram esterni e interni al chip, appare evidente come l’approccio RISC sia avvantaggiato sia dall’adozione dell’architettura a pipeline, sia dall’utilizzo di tipi di memoria embedded. Sono anche emerse considerazioni sul parallelismo di dati e istruzioni e sul loro rapporto.
Questo sarà elemento di un prossimo capitolo, in cui, oltre a sviluppare il discorso sulle architetture pipelined, introdurremo anche altri tipi di architetture che consentono l’esecuzione di più istruzioni per ciclo in generale, focalizzando sempre più l’attenzione sul calcolo di tipo parallelo. Arriveremo, così, a parlare di architetture superpipelined, superscalari, vliw, epic, smt, nonchè di sisd, misd, mimd e simd e faremo la conoscenza, un po’ più da vicino, con qualche architettura “reale”, per scoprire se e in che misura si è rimasti fedeli ai modelli teorici sin qui proposti.
Le architetture cisc più che semplificare la programmazione (tanto si usa un compilatore) avevano dei vantaggi di riduzione di memoria, visto che l’eseguibile cisc è più compatto dell’eseguibile risc.
Ora i vantaggi di memoria ci sono ancora ma sono diventati molto meno importanti visto il basso costo di ram e hard disk, mentre il vantaggio di programmazione non esiste.
Si che un compilatore per risc può essere più semplice nel back-end, ma l’efficenza di un eseguibile risc dipende fortemente dall’ottimizzazione del codice e quindi l’ottimizzatore di un compilatore risc deve essere complesso.
Un compilatore cisc deve gestire più istruzioni e modalità di indirizzamento, ma la cpu è “meno” (meno cum grano salis) dipendente dall’ottimizzazione del codice.
Il vantaggio riguarderebbe comunque solo i compilatori, per la stragrande maggioranza dei programmatori non c’è una differenza nel programmare per architetture risc o cisc.
Piccola osservazione, il PC non è + propriamente un registro che punta alla prossima istruzione da eseguire? (contiene l’indirizzo di memoria della prossima istruzione, non l’istruzione stessa). Ho letto di volata l’articolo, quindi potresti averlo precisato successivamente e magari mi è sfuggito.
@ Giogio
doverosa precisazione
@ The3D
vero, il PC è un registro contenente l’indirizzo della successiva istruzione (ho omesso indirizzo dell’). Ho provveduto alla correzione. Grazie per la segnalazione.
Se notate altre imprecisioni segnalatemele in modo che possa correggerle in serata (quando ho un momento libero). Purtroppo non ho avuto il tempo di rileggere l’articolo
“Quando si assegna un’istruzione ad una unità di calcolo, quindi, che sia un processore o una alu, innanzitutto si deve fornire l’indirizzo della cella di memoria da cui prelevare l’istruzione; quindi l’istruzione è caricata nel program counter da cui è inoltrata la richiesta di accesso alla ram (DMA) gestito dal memory controller e, successivamente, l’istruzione così ottenuta è inviata nell’instruction register; qui è decodificata e mandata in esecuzione.”
Credo che questo passo sia un poco ambiguo, se l’istruzione è gia caricata nn deve essere letta dalla memoria, se lavora su dati in memoria van letti prima. In ogni caso vengono letti dalla gerachia di cache che come ultima risorsa insiste su memory controller; di solito per DMA si intende un circuito separato di generazione indirizzi per fare trasfermimenti MEM/IO di vario tipo senza gravare sulla cpu.
@ Valerio
Corretto il periodo, grazie per la segnalazione.
Per quanto riguarda il resto del discorso, anche la cache è un tipo di memoria; inoltre, il discorso delle gerarchie vale soprattutto per le cpu, in cui si ha il passaggio di dati e istruzioni dalla ram di sistema ai vari livelli di cache (o alle memorie locali) e, quindi, ai registri. Per le gpu, invece, si ha il trasferimento diretto dei dati dalla ram ai registri di sola lettura. In quanto alle operazioni di dma. queste si hanno anche all’interno di architetture con elevato parallelismo. Ad esempio nelle architetture multicore con connesisone abus c’è sempre un MC che gestisce il traffico di dati e istruzioni all’interno del chip e dal chip verso l’esterno; così è per l’EIB del cell, ma così è anche per le gpu, in cui cluster di alu sono assimilabili ad un processore. Un esempio emblematico, che cito epr similitudine ocn il cell, è quello di R600 e RV670, dove si ha un ring il cui funzionamento è analogo a quello visto epr la cpu IBM con tanto di MC interno che gestisce il traffico di dati del bus e le richieste di accesso alla ram video.In quel caso, il trasferimento di dati avviene con la modalità: frame buffer->registri o registri->frame buffer
Non so se hai gia’ in cantiere altri articoli che sviluppano quanto scritto qui, ma ci sono alcune imprecisioni che possono fuorviare. Ne segnalo alcune (si e’ vero, oggi ho un po’ di tempo per fare il rompicoglioni :)
* gli accessi alla memoria possono essere eseguiti out-of-order, ma con estrema cautela: gli store di solito vengono eseguiti in order; i load no, ma bisogna stare attenti a non generare errori
* Quando parli di ALU piu’ complesse per processori CISC, ti riferisci ad architetture arcaiche? perche’ oggi, dopo il front-end (fetch + decode), il datapath di un processore CISC e’ praticamente uguale a quello di un RISC (le istruzioni CISC vengono spezzettate, fuse, ecc. per avere un simil RISC in dispatch+execute+retire: ad esempio, sono tutte architetture load/store, e le unita’ funzionali sono uguali)
* una pipeline non da’ per nulla minore complessita’ al chip: bisogna ridisegnare tutti gli stadi, spezzarli nei punti opportuni per evitare frammentazione interna ed esterna, inserire latch per dati e controlli, inserire percorsi di forwarding, meccanismi di pipe interlock… e questo solo per una “simple 5 stage pipeline” degli anni ’80 !
* una nota marginale sul costo di svuotamento della pipe nel recovery di un mispredicted branch: dici “… con architettura tra i venti e i trenta stadi, può far sprecare qualche centinaio di cicli solo per le operazioni di svuotamento e riempimento del pipeline.”. Il costo di svuotamento e’ molto meno (basta segnare come invalide nel ROB le istruzioni taggate col blocco del branch sbagliato); e’ il costo di “opportunita’” che e’ di centinaia di istruzioni (cioe’ centinaia di istruzioni utili, grossomodo il prodotto della profondita’ di pipe per la superscalarita’, che avrebbero potuto essere eseguite non sono eseguite, sprecando preziosi slot)
* non e’ vero che un processore CISC (stiamo parlando di x86, vero? :) ha bisogno di meno registri: e’ che se trova di meno a causa di scelte architetturali che si perdono nella notte dei tempi, quando i processori erano a 8 bit, i chip avevano mille transistor (e non mille milioni) , e potevi prendere un caffe’ aspettando che un segnale passasse da 0 a 1 :) . Se potessero riprogettarlo oggi, ci butterebbero dentro tranquillamente come minimo 64 registri architetturali. C’e’ da dire che coi registri rinominati (che sono molti di piu’ di quelli architetturali) risolvi abbastanza.
Altro commento volante: dici che nelle GPU c’e’ trasferimento diretto dalla RAM ai registri. E’ vero, non c’e’ cache (non nel senso inteso per i processori).
Ma si potrebbe discutere sul perche’. Ad es., Intel ha fatto scelte diverse: nell’architettura Larrabee hanno cache di primo e secondo livello (anche se con un’architettura un po’ diversa: se ben ricordo la L2 e’ divisa, ogni core ha il suo pezzo, e l’accesso e la coerenza viene mantenuta con un sistema a directory, su un ring che connette tutti i core).
In effetti credo non sia facile dire che un approccio e’ assolutamente meglio dell’altro. Ad es., e’ vero che le GPU, risparmiandosi la cache, possono investire tutta l’area in unita’ funzionali. D’altro canto, andare sempre in RAM (off-chip) ha un costo energetico elevato, molto piu’ che accedere ad una cache. Pero’, se non c’e’ abbastanza localita’…
@ pleg
i rompicoglioni preparati sono sempre ben accetti.
i MC sono programmati per gestire gli accessi in modalità OoO anche su chip rigorosamente IO (ad esempio cell o xenon). E, come sottolinei, sono soprattutto i load a necessitare della possibilità di essere gestiti OoO.
CISC. Si, mi riferisco alle archjitetture più vecchie anche perchè, come ho accenato nell’articolo e come hai sottolineato anche tu, ormai non ha più senso parlare di CISC a livello di microarchitettura, dal momento che le unità funzionali non si differenziano da quelle di un processore RISC.
Pipeline e complessità; infatti l’utilizzo di un’architettura pipelined aumenta la complessità del chip a livello circuitale generale ma sposta la complessità su altri livelli più facilmente gestibili. Ho scritto testualmente che diminuisce (e notevolmente) la complessità della singola unità e rende più sempplice la progettazione di chip complessi (il concetto è quello di aumentare la complessità del chip replicando strutture elementari).
Giusta la precisazione sul costo di svuotamento e riempimento
Processori CISC e registri. Non metto in dubbio che oggi progetterebbero mettendo dentro molti più registri, ma quello su cui ci si basa è il paradigma di programmazione e di microarchitettura CISC e RISC: il primo prevedeva operazioni di trasferimento diretto da e verso la memoria centrela, il secondo operazioni di load e store eseguite utilizzando i registri. Le odierne architetture CISC a livello di microarchitetutra sono parenti porssime di quelle RISC, quindi è naturale che anche l’utilizzo dei registri sia lo stesso.
GPU e larrabee. Nelle GPU la cache c’è e si utilizza anche allo stesos modo di come si usa nelle cpu. C’è, però, una notevole differenza: le gpu gestiscono operazioni a latenze elevatissime e che richiedono bandwidth molto maggiori di quelle delle cpu. Di conseguenza, sono progettate per gestire non decine ma diverse migliaia di thread in simultanea e, per farlo, hanno bisogno di un gran numero di registri per i trasferimenti diretti di dati e istruzioni verso le alu. Se progetto una gpu e devo dedicare dello spazio interno a delle unità di memoria, pertanto, privilegerò i registri e non le cache. Questo non vuol dire che le cache non ci siano: ad esempio sono presenti cache di piccole dimensioni per le istruzioni, delle cache di dimensioni ttutto sommato ragguardevoli per le texture (in quel caso hanno un sensno, in quanto le operazioni di texturing sono tra quelle a più alte latenze in assoluto). Della cache sono usate anche come frame buffer on chip per operazioni su dati ricorrenti (anche in questo caso hanno un senso, perchè oltre a velocizzare i tempi di accesso, risparmio banda passante). Nelle architetture ashader dedicati, tra uno stadio e l’altro si utilizzavano una sorta di buffer di accumulazione che avevano lo scopo di ridurre i cicli di idle di un determinato tipo di unità.
Anche i chip NV da G70 in poi (o addirittura da NV40) hanno cache di primo e secondo livello per le texture).
@yossarian
“Ho scritto testualmente che diminuisce (e notevolmente) la complessità della singola unità e rende più sempplice la progettazione di chip complessi (il concetto è quello di aumentare la complessità del chip replicando strutture elementari).”
Su questo ancora non sono d’accordo :)
Pipelinizzare non significa replicare strutture elementari: significa spezzare l’intero datapath in sezioni piu’ corte, da far lavorare in “parallelismo temporale”.
Replicare le strutture elementari e’ quello che si fa quando si va di parallelismo: processori vettoriali che replicano unita’ funzionali nelle lane, le GPU colle loro decine/centinaia di unita’ FP… in un certo senso, anche mettere piu’ core in un chip (non e’ un’unita’ elementare :) ma mettere 4 core in un chip e’ molto piu’ facile che quadruplicare la frequenza o la superscalarita’).
Quindi, e’ vero che in un processore moderno hai piu’ pipeline parallele (in fase di execute), che possono essere dei copia incolla (2x integer ALU , 2x load, 2x FPU …). Ma il concetto di pipeline non c’entra colla replica di strutture elementari.
@ pleg: quella che tu chiami correttamente “architettura che lavora in parallelismo temporale” è, fisicamente, una serie di elementi. Vero che pipelinizzare significa spezzare il datapath ma proprio in quest’ottica i cosiddetti datapath critici sono frazionati in una serie o in un parallelo di unità elementari o, comunque, più semplici. Dal punto di vista progettuale le cose funzionano esattamente come per la replica di intere pipeline o di interi core, solo in misura ridotta, ossia applicata a porzioni di un’architettura e non a tutta l’architettura) come ad esempio un processore multicore). Nell’esempio citato, il blocco S2 è stato frazionato in due (ma possono essere di più) blocchi più piccoli e architetturalmente più semplici che sono uno la copia esatta dell’altro. Esattamente come succede in un’architettura multicore o superscalare, solo in scala più ridotta.
[…] scorsa settimana abbiamo introdotto alcuni concetti tra cui, in particolare, quello di pipeline. Si è anche visto […]
[…] specifico, dobbiamo considerare che l’ARM era dotato di una pipeline a 3 stadi (fetch, decode ed execute). Questo vuol dire che in un determinato ciclo di clock il […]
[…] per lei aumentarne la velocità di esecuzione. Infatti uno dei suoi cavalli di battaglia, la pipeline corta (appena 3 stadi), era già stato sacrificato già da qualche tempo sull’altare della […]
Di fatto, l’approccio di tipo RISC nasce dal tentativo di semplificare le architetture dei chip attraverso un modello che prevedesse lo spezzettamento delle macro istruzioni RISC in tante istruzioni elementari.
Forse la frase corretta era “Di fatto, l’approccio di tipo RISC nasce dal tentativo di semplificare le architetture dei chip attraverso un modello che prevedesse lo spezzettamento delle macro istruzioni CISC in tante istruzioni elementari.