Dopo aver sezionato l’architettura x86 a caccia di elementi che possano essere classificati come legacy, è arrivato il momento di chiudere questa lunga serie di articoli con una sintesi e alcune riflessioni sull’argomento, riprendendo anche in parte quanto elaborato nel precedente pezzo sulle unità SIMD, che rappresenta una sorta di cartina al tornasole della situazione.
Ripropongo, per comodità, tutti i link ai precedenti otto articoli:
parte 1 (istruzioni 8086)
parte 2 (istruzioni 80186+)
parte 3 (FPU x87)
parte 4 (i segmenti)
parte 5 (i selettori)
parte 6 (istruzioni privilegiate)
parte 7 (istruzioni non privilegiate)
parte 8 (SIMD: MMX, 3DNow!, e… SSE)
e riprendo brevemente quanto proposto nel primo link, nella fase introduttiva, riguardo alla classificazione degli elementi legacy: la mappatura degli opcode delle istruzioni, le istruzioni più complesse e raramente utilizzate, e i famigerati segmenti (poi divenuti selettori).
Per quanto riguarda le istruzioni, abbiamo visto che, in fin dei conti, non sono poi tante, e per alcune la loro inutilità può certamente essere messa in discussione (mi riferisco, nello specifico, alle istruzioni “di stringa”).
Tolti i casi limite, è innegabile che le rimanenti debbano essere riconosciute (ma ciò attiene principalmente al decoder, che in questa serie non abbiamo trattato nello specifico), richiedano eventuali microistruzioni (chiamate anche micro-op) “RISC86” dedicate (una o più) per essere eseguite, eventualmente tramite microcodice per i casi più complicati (che non possono essere gestiti tramite l’esecuzione di due-tre micro-op).
Si tratta essenzialmente di transistor da impiegare appositamente allo scopo. Sono dell’idea che non ne siano necessari tanti (non ci sono molte istruzioni, e poche hanno un’elevata complessità), ma non essendo un esperto in microelettronica mi astengo da ogni altro giudizio. Certamente alcune, trattandosi di vecchiume, non verranno mai utilizzate, ma rimane il costo fisso della loro necessaria implementazione.
Un discorso simile vale anche per il terzo elemento discusso, i segmenti e i selettori. Dovendo, però, impiegare appositi registri (anche interni / “nascosti”), diverse altre istruzioni, e cambiare il comportamento di istruzioni esistenti che devono portare a termine operazioni anche molto complesse (per implementare i meccanismi dei gate), credo che il costo fisso di questa parte legacy dell’architettura x86 risulti decisamente più elevato rispetto alle altre.
Rispetto a queste, però, c’è da dire che segmenti e selettori impongono costi fissi non soltanto a livello implementativo, ma anche a runtime, durante la normale esecuzione, e questo anche nel caso in cui non se faccia sostanzialmente mai uso (mentre le vecchie istruzioni possono non essere mai eseguite, poiché non appaiono mai nel codice dei programmi).
Ricordiamo, infatti, che la segmentazione richiede appositi controlli sui limiti dell’area di memoria che indirizzano, sul tipo di accesso consentito per quest’area, e concorrono pure al calcolo dell’indirizzo virtuale tramite un indirizzo base (che si va poi a sommare all’offset normalmente calcolato dalle istruzioni che indirizzano la memoria).
Tutto ciò va effettuato “contemporaneamente” (semplifico il discorso) per un massimo di tre segmenti/selettori, per ogni singola istruzione che dev’essere eseguita: uno per il codice (l’opcode va caricato dalla sua area di memoria) e al più due per l’indirizzamento della memoria (sorgente e/o destinazione di alcune istruzioni).
Abbiamo visto che, alla fine, il tutto viene realizzato facendo uso di poca logica (comparatori, sommatori, porte logiche), per cui non richiederanno molti transistor per la loro implementazione, ma è certamente vero che si tratti di logica sempre attiva e che non può essere spenta, per cui incide sempre nei consumi del processore (anche se bisognerebbe vedere in che misura rispetto a tutto il resto).
Tutt’altro peso ha, invece, la mappatura delle istruzioni, che influenza ovviamente il decoder. Abbiamo visto in due articoli (che trovate qui e qui) quanto complicato sia lo schema di decodifica delle istruzioni, e quanti milioni di transistor siano richiesti per ottemperare all’arduo compito.
In estrema sintesi, le problematiche derivano dal:
- ricorso al famigerato concetto dei prefissi per cambiare alcune impostazioni / comportamento delle istruzioni
- introduzione di speciali opcode che fungono da prefisso per estendere la tabella degli opcode
- schema di definizione degli operandi registro e memoria, il quale richiede dei byte che si aggiungono alla fine di quelli usati per specificare l’opcode vero e proprio, e il cui numero totale è determinato non dall’opcode, ma esclusivamente dal byte di estensione / definizione degli operandi
- possibilità di specificare un valore immediato (a 8, 16, o 32 bit)
In parte lo abbiamo anche visto nell’ultimo pezzo, in cui si discuteva del legacy legato alle istruzioni SIMD (MMX, 3DNow! ed SSE): si fa pesante uso dei prefissi per cambiare il comportamento delle istruzioni SSE (ad esempio per specificare di voler operare su dati scalari o packed/vettoriali), e sono stati introdotti due speciali “sub-opcode” per aggiungere due altre tabelle per i nuovi opcode.
Se consideriamo che per la sola sezione di decodifica il primo Pentium impiegava quasi un milione di transistor e il PentiumPro circa 2,2 milioni, ed entrambi non dovevano tener conto di alcuna estensione SIMD, è facile supporre che l’introduzione di MMX, ma soprattutto di SSE ed SSE2, abbia fatto lievitare ulteriormente il costo del decoder, sia in termini di transistor impiegati che di consumo (quest’unità, poi, è quasi sempre attiva).
L’introduzione delle estensioni SIMD AVX da parte di Intel rappresenta un’ulteriore dimostrazione, se ce ne fosse ancora bisogno, di come sia proprio la decodifica delle istruzioni il più grosso tallone d’Achille che l’architettura x86 si porta dietro.
Con AVX, infatti, Intel ha introdotto due speciali prefissi (non c’è altro da fare, purtroppo: è l’unico modo per estendere agevolmente quest’ISA), rispettivamente di 2 e 3 byte, che consentono di mappare qualunque opcode SIMD SSE (quindi non MMX) in maniera molto più semplice (i prefissi SIMD 66/F2/F3 sono già “inglobati”).
Il tutto mettendo anche a disposizione parecchio spazio per estendere ulteriormente l’ISA senza richiedere l’introduzione di altri prefissi e/o di “sub-opcode“. E’ stata aggiunta, inoltre, la possibilità di specificare un terzo registro, così da poter avere istruzioni dotate di 3 o 4 operandi, similmente a ciò che avviene in altre architetture (RISC).
In questo modo tutte le istruzioni SIMD occupano 3 o 4 byte “di base” (2 o 3 di prefisso AVX, a cui si aggiunge poi il byte di opcode vero e proprio), mentre prendendo in considerazione soltanto le estensioni SSE (che poi sono le più usate; è raro trovare codice MMX ormai) si va dai 2 ai 4 byte.
Sulla carta può sembrare un peggioramento, visto che il codice SSE per le medesime istruzioni può risultare anche più denso (e mediamente lo è, statistiche alla mano), ma c’è da dire che, sfruttando la possibilità di specificare un terzo registro, codice appositamente compilato per AVX dovrebbe richiedere meno istruzioni e, quindi, risultare più denso ed efficiente.
AVX non risolve certo tutti i problemi, visto che non elimina tutti i prefissi (inoltre ne aggiunge due nuovi), e inoltre l’indirizzamento della memoria resta abbastanza macchinoso, ma si tratta comunque di una buona soluzione (con Larrabee / Knights Corner Intel ha seguito un approccio simile per la nuova estensione SIMD a 512 bit, ma con un prefisso di 4 byte), specialmente in ottica futura, per lo meno per l’unità SIMD.
Tolto questo (che comunque ha un suo peso), per tutto il resto ci sarà sempre un costo fisso, a livello implementativo e/o a runtime, che l’architettura x86/x64 dovrà continuare a pagare nei confronti delle altre, e che può incidere più o meno pesantemente, a seconda dello specifico ambito applicativo.
L’integrazione dei core ARM nelle CPU Opteron di AMD è un segnale molto chiaro in merito, che dovrebbe far riflettere molto. Evidentemente il mercato ha bisogno non soltanto delle prestazioni, e AMD non è in grado di offrire un’alternativa valida. Questo non significa che lo stesso debba necessariamente valere per Intel, sebbene la situazione non sia poi molto diversa, dovendo entrambe dar conto al legacy, ineliminabile, della loro architettura.
Aggiungo un’ultima riflessione, e concludo, che riguarda anche altri costi, quelli di progettazione e testing, che non si possono certo trascurare quando parliamo di un progetto, specialmente di queste dimensioni e importanza.
Un’architettura così complessa, e in particolare il suo decoder, richiede notevoli sforzi per essere progettata e testata, perché deve tenere conto di tutto quanto abbiamo analizzato finora. In particolare, visto che parliamo di silicio, un malfunzionamento potrebbe creare gravi problemi, che non sono sempre facilmente risolvibili con un aggiornamento del microcodice (operazione che fanno i BIOS delle schede madri all’avvio).
Alla luce di tutto ciò, forse è arrivato il momento di una svolta, di un forte cambiamento, che non richieda obbligatoriamente il salto sul carro di ARM, come ha fatto AMD…