Il definitivo RISC vs CISC – 5: Come i CISC… sono rimasti CISC!

Affrontata, nel precedente articolo, la trasformazione dei RISC in CISC, passiamo adesso a vedere come i CISC siano rimasti, invece, esattamente gli stessi, a dispetto delle leggende metropolitane che circolano da parecchio tempo.

Prendendo, infatti, i quattro pilastri su cui si basano i RISC (i quali sono stati riportati nel secondo articolo) sappiamo che è sufficiente che non ne sia valido anche soltanto uno affinché non si possa più parlare di RISC e, conseguentemente, che ci troviamo di fronte a un CISC.

Conservare la propria identità

A questo punto la verifica dovrebbe essere abbastanza banale. Basterebbe, infatti, prendere un qualunque processore che venga definito CISC e andare a controllare uno per uno le definizioni dei quattro pilastri. Ovviamente non parlo soltanto dei vecchi processori, ma anche degli ultimissimi arrivati: non v’è differenza alcuna, definizioni alla mano.

Dunque se un processore ha molte istruzioni, oppure ne ha che richiedano più cicli di clock per il completamento della loro esecuzione, o se queste hanno una lunghezza variabile (anziché fissa), o ancora se alcune consentono di accedere direttamente alla memoria (invece di delegare il compito soltanto a quelle di load / store), possiamo senza dubbio alcuno affermare che si tratti di un CISC.

Come si può vedere, non servono chissà quali conoscenze tecniche per poter applicare questi quattro concetti elementari e capire a quale macrofamiglia appartenga un processore. Ma, soprattutto, per comprendere che, al contrario dei RISC, la coerenza è sempre stata di casa nei CISC

Pertanto non ha alcun senso affermare che non si possa più parlare di RISC e CISC, in quanto ci sarebbe stata una convergenza e che, ormai, sarebbero dei concetti che si sarebbero “mischiati”. Queste bislacche tesi vanno certamente rigettate, alla luce delle più volte menzionate definizioni.

Si potrebbe affermare, al più, che ciò si potrebbe applicare soltanto ai RISC, i quali hanno dimostrato di aver radicalmente cambiato la loro natura, divenendo essenzialmente dei CISC. Ma il viceversa non è mai avvenuto, checché se ne dica.

Istruzioni veloci = RISC?!?

Qualcuno potrebbe obiettare che, guardando ai tempi d’esecuzione, le istruzioni dei processori CISC siano ormai da tempo eseguite per la maggior parte dei casi in un solo ciclo di clock, e questo sarebbe un chiaro segnale della loro “convergenza” ai RISC.

In realtà i processori CISC non sono stati necessariamente sinonimo di istruzioni lente, che richiedevano parecchi cicli di clock: tutto dipendeva, infatti, dalla particolare implementazione / microarchitettura in questione.

Se prendiamo uno dei processori CISC più vecchi, il 6502 (classe 1975), possiamo notare come eseguire la somma dell’accumulatore con un valore immediato (a 8 bit) richieda, sì, 2 cicli di clock, ma che sono necessari per la lettura dei due byte (l’opcode dell’istruzione e il valore immediato) e con un’interfaccia verso la memoria a 8 bit non si poteva certo pretendere tempi inferiori ai due cicli!

Con istruzioni costituite da un solo byte ci si aspetterebbe, compatibilmente col numero di accessi alla memoria, la richiesta di un numero di cicli di clock ancor più ridotto. Ciò non avviene col 6502 perché, ad esempio, l’istruzione di scorrimento a sinistra di uno dell’accumulatore (ASL) necessita di due cicli di clock.

Si tratta, però, di limiti intrinseci del progetto originale (il quale, in ogni caso, sfoggiava già l’implementazione di una pipeline, com’è chiaramente evidente), che coi successori sono stati migliorati. Infatti nel 65CE02 la stessa istruzione richiede un solo ciclo di clock. Molte altre sono state altrettanto ottimizzate, con le più comunemente utilizzate che sono dominate, per i tempi d’esecuzione / cicli di clock, dal numero di accessi alla memoria.

Ed è anche normale che sia così, perché i progressi tecnologici non sono di certo stati appannaggio dei soli RISC, ma ne ha beneficiato qualunque dispositivo, com’è possibile verificare rapidamente controllando i manuali dei vari processori CISC che hanno avuto dei successori.

Il mito del RISC all’interno dei CISC

Ma la più martellante nonché comune contestazione in tal senso rimane quella che vorrebbe la presenza / adozione di processori RISC all’interno di quelli CISC, coi primi a essere gli effettivi esecutori delle istruzioni / operazioni dei secondi.

Inutile dire che ci troviamo di fronte a una delle più diffuse fallacie logiche: la falsa pista (o depistaggio). Questo perché rimane un tentativo di depistaggio dall’argomento concreto, ossia la definizione di RISC (e, di conseguenza, quella di CISC) che è l’unica cosa che dovrebbe interessare quando bisogna classificare un processore.

Infatti l’ultra citata definizione è sufficientemente chiara e intelligibile da consentire di discriminare in maniera assolutamente precisa e incontestabile cosa sia un RISC e cosa un CISC, dal punto di vista sia architetturale (ISA) sia microarchitetturale. Esiste una definizione? Che la si applichi! Sic et simpliciter

La situazione, comunque, non cambierebbe ugualmente anche nel caso in cui ignorassimo temporaneamente la definizione di RISC e considerassimo come vero il concetto di roba “mischiata” per le due macrofamiglie.

La ragione è alquanto semplice: il “RISC interno” (SIGH!) lavora bene esclusivamente grazie all’architettura CISC, la quale consente di “comprimere” meglio il codice (riducendone la dimensione e, di conseguenza, lo spazio occupato in tutta la gerarchia di memoria. Quindi migliorando la famosa densità di codice di cui abbiamo ampiamente parlato) nonché il “lavoro utile” che viene eseguito per portare a compimento le varie attività / operazioni.

Il pesce è morto fuori dall’acqua

Una dimostrazione pratica non è possibile, in quanto non esistono processori (di cui abbia conoscenza) che abbiano implementato / esposto esternamente (quindi con la loro ISA direttamente utilizzabile) questi “RISC“, ma è sufficiente fare delle considerazioni per arrivare alla conclusione che sarebbero un assoluto fallimento.

Nello specifico, è bene notare che tali istruzioni in genere siano molto grandi e occupino parecchio spazio. Un esempio che espone efficacemente il concetto è rappresentato dal famoso Pentium III, di cui si sa che la dimensione delle cosiddette µOP è di 118 bit (pari a poco meno di 15 byte). Questo nel caso abbastanza comune (cioè la maggior parte delle istruzioni), ma va doverosamente precisato che ci sono diverse istruzioni x86 che generano più µOP (e non sono affatto rare!).

Mettiamoci nell’ipotesi che esista un processore basato su queste µOP, quindi con programmi compilati appositamente e le cui istruzioni coincidano esattamente con esse. Senza che il ragionamento perda di consistenza e per semplificare il discorso, supponiamo che tali istruzioni siano lunghe 128 bit (10 bit in più; per renderle potenza del due), pari a poco più di un byte aggiuntivo.

Adesso immaginiamo questo processore all’opera, quindi nell’atto di eseguire queste istruzioni che occupano 16 byte l’una. Considerato che x86 ha istruzioni lunghe mediamente 3 byte, vuol dire che lo spazio occupato dal codice di questo fantomatico processore RISC basato sulle sue µOP sarebbe cinque volte quello dell’equivalente x86 (15 / 3 = 5). Il che già di per sé è un valore esorbitante (persino peggiore di Itanium!).

Si potrebbe ingenuamente pensare che una cache istruzioni larga cinque volte dovrebbe essere sufficiente per “sanare” il problema. Premesso che passare da 32kB a 160kB, ad esempio, sarebbe già abbastanza folle da far immediatamente abbandonare l’idea a qualunque persona sana di mente, una tale scelta ignorerebbe, in ogni caso, due fattori tutt’altro che trascurabili.

Il primo è che l’aumento di dimensione di un fattore cinque coinvolge, come anticipato, l’intera gerarchia della memoria. Quindi dalla cache istruzioni (L1) alle cache L2 e L3, oltre che dalle entry TLB, passando infine dalla memoria di sistema e dal relativo consumo di banda (il cui fabbisogno sarebbe quintuplicato per le esigenze di tali cache).

Il secondo, e ben più importante, è che le prestazioni della cache per il codice non sono affatto lineari rispetto alla dimensione del codice, ma esponenziali. Riporto nuovamente e brevemente i risultati dalla tesi di uno dei progettisti di RISC-V:

Waterman shows that RVC fetches 25%-30% fewer instruction bits, which reduces instruction
cache misses by 20%-25%, or roughly the same performance impact as doubling the instruction
cache size
.

Se ridurre la dimensione del codice del 25-30% (per RISC-V. Ma un discorso simile vale per qualunque architettura) fosse rozzamente equivalente allo stesso impatto prestazionale che comporterebbe una cache di dimensione doppia, varrebbe anche il viceversa: un codice più grande del 33% (circa) richiederebbe cache di dimensione doppia per avere un impatto prestazionale all’incirca uguale.

Considerato che il codice per questa architettura teorica è cinque volte quello x86, ciò significherebbe dover utilizzare cache per il codice di dimensione fra le 32 e le 64 volte circa rispetto a quest’ultima, per potere avere prestazioni simili.

Direi che possiamo tranquillamente calare un velo pietoso, e questo senza nemmeno considerare che x86 generi più µOP per le sue istruzioni (peggiorando la situazione, ovviamente)…

Contrariamente ai proclami che alcune volte si leggono in giro, la conseguenza che se ne trae è anche un’altra: l’architettura di un processore conta, eccome! Con ciò mi riferisco, quindi, a quanto sia “esposto all’esterno”: ai programmatori, ai compilatori, ecc..

Come poi essa sia implementata è una questione legata puramente alla microarchitettura, la quale rimane pur sempre un dettaglio interno. E, in qualità di dettaglio interno, significa anche che sia del tutto irrilevante riguardo la questione RISC vs CISC.

Ma le µOP non sono istruzioni RISC!

In tutta questa discussione l’assunto è stato quello che le µOP fossero istruzioni di un processore RISC. Cosa che, invero, risulta smentita anche da alcuni progettisti di processori x86. Uno su tutti, Bob Colwell, che è stato a capo del team che ha sviluppato il primo processore Intel facente uso di µOP: il Pentium Pro (noto anche come P6).

Il quale ha affermato:

Intel’s x86’s do NOT have a RISC engine “under the hood.” They implement the x86 instruction set architecture via a decode/execution scheme relying on mapping the x86 instructions into machine operations, or sequences of machine operations for complex instructions […] The “micro-ops” that perform this feat are over 100 bits wide, carry all sorts of odd information, cannot be directly generated by a compiler, are not necessarily single cycle. But most of all, they are a microarchitecture artifice — RISC/CISC is about the instruction set architecture. […] The micro-op idea was not “RISC-inspired”, “RISC-like”, or related to RISC at all. It was our design team finding a way to break the complexity of a very elaborate instruction set away from the microarchitecture opportunities and constraints present in a competitive microprocessor.

Non penso sia necessario aggiungere altro, perché l’estratto qui sopra riportato risulta abbastanza eloquente.

A corollario di ciò aggiungo che, essendo, questi, elementi microarchitetturali, stiamo parlando di soluzioni adottate per gli specifici processori in cui sono implementate e che non necessariamente i loro successori le adotteranno in toto o anche con qualche modifica, perché potrebbero arrivare nuove idee in grado di stravolgerle.

Si tratta di ovvietà ma che, considerato il ciarpame che circola sull’argomento (specialmente su internet), penso risulti doveroso evidenziarlo per bene.

Un esempio in merito è rappresentato dalle implementazioni molto diverse che hanno adottato gli ingegneri di Intel e di AMD. Com’è possibile vedere dagli studi sulle microarchitetture del famoso Agner Fog, Intel preferisce suddividere istruzioni complesse in µOP più semplici, mentre quelle utilizzate da AMD sono più complesse (e, quindi, ne vengono generate di meno).

Basti andare a controllare i dati relativi all’istruzione ADD, ad esempio: AMD utilizza una sola µOP (che chiama MacroOP nel suo gergo) nella maggior parte dei casi (succede anche in tutti i casi, in diverse sue microarchitetture), mentre Intel va da uno a quattro (a seconda della complessità della specifica versione dell’istruzione).

Ora, sarebbe interessante che qualcuno mi spiegasse in che modo potrebbe qualificarsi come RISC un processore che fosse in grado di eseguire la seguente istruzione (di ben 12 byte):

ADD QWORD PTR [RBX + RAX * 8 + 0xDEADBEEF], 0x0C0FFEE0

in due o addirittura una sola µOP/MacroOP! La domanda era retorica, ovviamente…

Conclusioni

L’ultima parte dell’articolo è servito per analizzare e smontare il cumulo di sciocchezze che circolano da parecchio tempo, specialmente quando si parla di CISC. Non era fondamentale poiché, come già sottolineato, ciò che conta è la definizione di RISC e, di conseguenza, quella di CISC, che sono più che sufficienti a dirimere la questione. Ma ho preferito affrontarle per non lasciare più nulla in sospeso o che alimenti ancora dubbi in merito, riportando fatti concreti a loro supporto.

Il prossimo articolo chiuderà la serie facendo il punto della situazione e aggiungendo alcune riflessioni finali su questa diatriba di così lungo corso.

Press ESC to close