APX: la nuova architettura di Intel – 6 – Costi implementativi

Discusso della densità di codice nel precedente articolo, rivolgiamo i nostri pensieri, adesso, al peso che tutte queste modifiche per implementare APX possano comportare. Nello specifico, Intel afferma che l’area di silicio non venga affetta in maniera “significativa”, come pure il consumo (di corrente) di un core.

Non guardare al core, ma al decoder!

Sappiamo, però, che i core dei processori moderni sono molto grandi (specialmente quelli dei processori Intel), per cui e se magari fosse vero che l’impatto complessivo non sarebbe così sensibile di per sé, è anche vero che non bisogna spalmarlo su tutta l’area del core, ma bisogna considerare, invece, quella in cui incide, ossia principalmente il il blocco di decodifica delle istruzioni (che chiamerò decoder, per semplicità).

C’è anche da dire che, a causa del progetto molto obsoleto (l’8086. Che, però, è stato realizzato in altri tempi e con ben altre esigenze. Contestualizzare è sempre importante!) a cui sono state aggiunte via via sempre più modifiche (preferirei il termine “pezze”, che risulta certamente più accurato), in passato era il singolo elemento che è arrivato a occupare più area (si stima circa il 40% col Pentium Pro!), con diversi milioni di transistor impiegati solo per questo scopo.

Col tempo altri elementi (in particolare le cache) hanno preso il sopravvento quanto ad area occupata / transistor impiegati, mentre è già da parecchio che i decoder si siano sostanzialmente assestati e presumibilmente continueranno a usare budget simili per il numero di transistor impiegati (nell’ordine di alcuni milioni).

Il problema è che, però, sono anche uno degli elementi più attivi di un core e che consuma più corrente, contribuendo considerevolmente anche al budget termico del chip, per cui aumentarne un po’ la complessità potrebbe incidere in maniera apprezzabile.

Questo è il motivo per cui in precedenza ho affermato che sia necessario non considerare l’aumento dell’area di silicio che APX comporti confrontandolo con tutto il chip (o core), ma bisognerebbe farlo prendendo in considerazione, invece, la sola area del decoder. Perché quello che è un cambiamento “non significativo” per l’intero chip si potrebbe rivelare, invece, molto significativo tenendo conto del solo decoder, con tutte le implicazioni che ciò comporterebbe (consumi, in particolare).

L’impatto di APX nel dettaglio

L’implementazione di APX, infatti, non è banale a livello di decoder, perché c’è un bel po’ di logica da trasformare in transistor. Se l’introduzione del nuovo prefisso REX2 può essere relativamente semplice (è molto simile a REX, con l’aggiunta che non deve tenere conto di nessun altro prefisso dopo di lui, poiché la mappa di opcode da utilizzare è già in esso incorporata), le modifiche a quello EVEX sono, invece, decisamente più complicate.

Per comodità riporto la struttura del prefisso EVEX originale (come quando è stato introdotto da AVX-512):

76543210
Byte 0 (62h)01100010
Byte 1 (P0)R̅’00m1m0P[7:0]
Byte 2 (P1)W32101p1p0P[15:8]
Byte 3 (P2)zL’LbV̅’a2a1a0P[23:16]

Infatti questo prefisso ha assunto, adesso, una duplice forma nonché funzionamento (come è stato illustrato nel primo articolo), poiché si comporta in maniera abbastanza diversa a seconda che l’istruzione “promossa” sia una delle due nuove condizionali (CCMP e CTEST) oppure una qualunque altra.

Di seguito la struttura che EVEX assume esclusivamente per queste due istruzioni:

76543210
Byte 0 (62h)01100010
Byte 1 (P0)3334B4100P[7:0]
Byte 2 (P1)WOFSFZFCF4p1p0P[15:8]
Byte 3 (P2)000ND=1SC3SC2SC1SC0P[23:16]

Quindi il decoder, dopo aver “capito” di avere a che fare con le istruzioni promosse (perché EVEX ha specificato l’uso della mappa 4. Maggiori dettagli si possono trovare nel primo articolo) deve controllare se si trovi davanti a una delle due istruzioni citate oppure no, e prendere strade completamente diverse per decidere in che modo utilizzare i bit che si porta dietro (che servono per “espandere” il comportamento delle istruzioni) e come generare le micro-op da dare finalmente in pasto al backend.

La complicazione maggiore si ha con le altre istruzioni (non le due nuove condizionali), perché deve tenere conto del fatto che possano essere estese o meno a ternarie/binarie (da binarie/unarie), ma in particolare rimappare internamente gli opcode delle istruzioni è un’operazione abbastanza onerosa in termini di risorse utilizzate.

La struttura di EVEX, in quest’ultimo caso, è la seguente:

76543210
Byte 0 (62h)01100010
Byte 1 (P0)3334B4100P[7:0]
Byte 2 (P1)W32104p1p0P[15:8]
Byte 3 (P2)000ND4NF00P[23:16]

Questa rimappatura è necessaria, perché la mappa 4 contiene le istruzioni sia della mappa 0 sia della mappa 1, che ovviamente non possono coesistere (la mappa 4 contiene 256 configurazioni, mentre le mappe 0 e 1 ne mettono a disposizione 256 ciascuna, quindi per un totale di 512). Per essere precisi, quelle della mappa 0 non necessitano di alcuna rimappatura, perché utilizzano già il loro opcode originale (come già spiegato nel primo pezzo).

I problemi sorgono con quelle della mappa 1, per le quali sono state utilizzate le configurazioni lasciate libere (inutilizzate) della mappa 0. L’operazione di rimappatura comporta, dunque, per prima cosa il riconoscere quali delle 256 configurazioni della mappa 4 appartengano all’originale mappa 1, per poi effettuarne, soltanto in questo caso, la conversione nell’opcode originale.

Non si tratta, in definitiva, di considerare soltanto il numero di transistor impiegati (che non saranno molti complessivamente, rispetto a tutti quelli già usati nel frontend), ma del fatto che questi siano elementi molti attivi. E’ ben noto, infatti, che il decoder sia uno degli elementi particolarmente energivori di un core, che contribuisce sensibilmente ai consumi, e queste modifiche tutt’altro che semplici non faranno che aumentarli.

Non tutto il male viene per nuocere

Guardare esclusivamente ai consumi complessivi di un core (o, più in generale, di un processore che ne abbia più d’uno) non sarebbe, comunque, corretto per valutare l’impatto che questa nuova estensione abbia sui consumi registrati durante l’esecuzione del codice (quindi sul suo reale comportamento).

Un altro elemento estremamente importante di cui si deve tener conto sono, infatti, le prestazioni: quanto velocemente venga portato a termine un determinato compito. Questo perché la misura dell’efficienza di un processore non può prescindere dalla combinazione dei consumi e del tempo impiegato.

Il concetto è veramente banale, ma ho visto spesso che, quando si parla di processori, si tenda a identificarne/confonderne l’efficienza coi consumi (tante volte prendo quelli massimi), tralasciando completamente le prestazioni. Il che risulta chiaramente (ma non altrettanto ovviamente, purtroppo) sbagliato.

Questo perché un processore può consumare più di un altro, ma se impiega meno tempo nell’eseguire i compiti che gli vengono dati, allora può anche arrivare a consumare meno tenendo conto dell’intera elaborazione.

Nello specifico, Intel ha dichiarato che con APX vengono eseguite circa il 10% in meno di istruzioni (nei risultati preliminari di test interni che ha effettuato con la celeberrima suite SPEC2017). Purtroppo non sono state fornite ulteriori informazioni (sarebbe stato utile capire in quali parti del codice ciò sarebbe inciso. Ad esempio se dentro o fuori i cicli e, in generale, nelle parti più critiche).

In ogni caso l’azienda ha dichiarato pure che i consumi non siano aumentati in maniera significativa (sebbene non ne venga fornita alcuna misura), per cui è perfettamente lecito aspettarsi che l’aumento dovuto alla maggior complessità dell’implementazione di APX sia stato bilanciato dal fatto di eseguire meno istruzioni (perché si riesce a fare “più lavoro utile”) e, quindi, in ultima analisi dal minor tempo di esecuzione che ha portato il processore a consumare complessivamente meno (di ciò che si sarebbe potuto aspettare).

Il che non deve affatto sorprendere: se la densità di codice è un “Santo Graal” delle architetture degli elaboratori, lo stesso si può dire riguardo le prestazioni in single core/thread. Entrambi, come abbiamo visto, hanno profonde implicazioni e, quindi, sono oggetto di ricerca e sviluppo da tantissimo tempo, coi produttori di processori da sempre impegnati a cercare di migliorare entrambi.

Per completezza va detto che anche il tempo di risposta è un’altra ricercatissima caratteristica di un’architettura (di una microarchitettura, più precisamente), che è parzialmente legata alle altre due citate prima. Infatti se un progetto richiede l’esecuzione di determinati compiti entro certi periodi di tempo e un processore non riesce a soddisfare questi vincoli, può essere efficiente quanto si vuole, ma ha fallito (e, quindi, non è adatto allo scopo!).

Nel prossimo articolo proporrò dei miglioramenti ad APX, che consentono sia di semplificarne considerevolmente l’implementazione sia di ridurre nettamente la lunghezza delle istruzioni che fanno uso di quest’estensione, migliorando in maniera significativa, quindi, la densità di codice.

Press ESC to close