Per natura siamo predisposti a effettuare confronti non appena ce ne sia occasione e, com’è ovvio che sia, ciò vale particolarmente nel mondo dell’informatica. La quale, come ben sappiamo, è dominata dai numeri, che non sono soltanto quelli che vengono elaborati dai nostri computer o dispositivi, ma anche quelli che utilizziamo per classificarli in qualche modo (Ghz, core, GB, …).
Il cuore pulsante della “ferraglia” che utilizziamo, di qualunque tipo essa sia, è rappresentato dai processori che, nonostante negli ultimi anni abbiano assunto un ruolo minoritario rispetto ad altri coprocessori e unità di calcolo che gli sono stati affiancati (GPU
, DSP
, e di recente anche acceleratori per l’IA), conservano ancora un ruolo centrale (anche perché continuano a coordinare tutti questi fedeli servitori).
La storia ci ha consegnato una quantità notevole di processori di tutti i tipi, coi loro pregi e difetti, punti di forza e di debolezza, per cui è stato abbastanza naturale “confrontarli”. Cosa che abbiamo fatto anche in queste pagine parecchi anni fa, con un paio di articoli che amo richiamare in quanto sono rappresentativi di quale genere di “misure” e confronti siano stati oggetto (e sono ancora):
E’ più veloce un 6502 a 1Mhz o uno Z80 a 3,5Mhz?
Dal mito dei Mhz a quello dei bit…
Proprio nell’ambito dei processori c’è una diatriba che dura ormai da più di 40 anni e che contrappone due diverse tipologie, chiamate RISC
e CISC
, le quali, però, sarebbe forse più corretto chiamare macro-famiglie (in alcuni casi, dove il contesto sia intelligibile, per semplicità mi riferirò a loro più generalmente come “famiglie”) di architetture (e, parzialmente, anche di microarchitetture. In un altro articolo ci saranno dettagli in merito), in quanto un determinato processore appartiene soltanto a una delle due. Le quali formano, pertanto, due partizioni distinte e separate.
Livelli di astrazione diversi
Prima di entrare nel vivo della diatriba è, però, molto importante precisare che parlare di RISC
e CISC
riguarda un determinato livello di astrazione. Ancor più chiaramente, parliamo proprio della cima della “piramide”, ossia del livello più alto (e più astratto, quindi): la macro-famiglia, per l’appunto.
Scendendo di un grandino troviamo, ovviamente, la famiglia (di processori). Un esempio è rappresentato dalle arcinote x86
e ARM
, rappresentanti di CISC
e RISC
, rispettivamente (anche se sul conto di ARM
ci sarà molto da dire).
Infine, e scendendo ancora un altro gradino della piramide, troviamo l’ultimo livello di astrazione che è rappresentato dal membro della famiglia. Anche qui, un esempio sono l’Atom N450
e il Cortex-A9
, i quali fanno ovviamente parte di x86
e ARM
.
In realtà si sarebbe potuto inserire un ulteriore livello di astrazione fra il secondo e il terzo, che avrebbe permesso di arricchire e meglio differenziare le categorie. Ad esempio per x86
avremmo potuto introdurre 8086
, 80186
, 80286
, 80386
, .., x86-64
quali architetture “base”, con l’Atom N450
che sarebbe stato, quindi, un membro di x64
. Le equivalenti per ARM
sarebbero, invece, ARMv1
, ARMv2
, ARMv3
, …, ARMv9
, Cortex-A
, Cortex-M
, Cortex-R
, col Cortex-A9
facente parte di Cortex-A
, ovviamente.
Preferisco, però, ignorare quest’altro livello di astrazione, perché non apporta alcun contributo all’obiettivo dell’articolo. Il quale è, invece, volto a evidenziare un concetto che dovrebbe essere ovvio, ma che spesso non lo è affatto: parlare a un certo livello di astrazione non autorizza a trarre conclusioni su quelli più alti. Si tratta chiaramente di una fallacia logica, sebbene sia piuttosto comune, purtroppo.
Come confrontare: togliere tutte le variabili eccetto una
Altra cosa di non poco conto è il fatto che, da tempo, non esistano più processori “nudi e crudi”. Infatti ciò che finisce nelle schede madri è classificato come CPU, che può integrare anche più di un processore. Ma, anche nel caso in cui si avesse un solo processore, sarebbero presenti anche altri elementi chiamati “uncore” in gergo. A cui si aggiungono, poi, le periferiche esterne, che possono anch’esse influenzare le prestazioni nei test o cosa possa essere integrato nel core (perché ci saranno meno risorse a disposizione).
Come si può vedere, cercare di fare confronti anche allo stesso livello di astrazione (l’ultimo: quello più in basso, che identifica un preciso membro di una famiglia) risulta estremamente difficile proprio perché, oltre al singolo processore, c’è ben altro di cui si dovrebbe tenere conto. La tripletta ISA
+ microarchitettura + processo di produzione (che identifica un processore per un ben preciso chip) non è, quindi, più sufficiente per effettuare confronti, i quali risulterebbero “viziati” da quanto esposto.
Idealmente, quando si confrontano due CPU, si vorrebbero eliminare tali differenze avendo per entrambe, quindi, esattamente gli stessi elementi, con l’unica differenza rappresentata dai diversi core. Sfortunatamente questo è, ormai, praticamente impossibile.
Gli ultimi processori a consentire questo tipo di confronti sono stati gli x86
che hanno usato il famosissimo socket 7 di Intel (il socket 8 ha avuto una ristrettissima adozione): bastava cambiare la CPU, metterne un’altra, e ripetere gli stessi test usando esattamente la stessa piattaforma.
Confronti su stessa piattaforma e livello di astrazione (il terzo): il membro della famiglia — Il livello utente
A questo punto ci si potrebbe chiedere se avesse ancora senso effettuare confronti pur supponendo di avere a disposizione una piattaforma comune (stesso socket / ambiente), perché si possono avere CPU completamente diverse (a livello di elementi presenti all’interno) pur facenti parti della stessa architettura / ISA
.
Confrontereste una CPU in-order a due vie con un’altra sempre a due vie ma out-of-order, pur se della stessa famiglia? Potrebbe anche avere senso, ma ovviamente dipenderebbe interamente dall’obiettivo che si volesse ottenere con questo tipi di test e misurazioni.
Anche avendo due CPU in-order della stessa famiglia, ad esempio, potrebbero in ogni caso avere elementi dell’uncore completamenti diversi (cache L2 & L3, controllore della memoria integrato, controllore PCI/PCI-Express integrato, memoria embedded, memoria saldata sul package, ecc.), come pure elementi diversi del core stesso (cache L0/micro-op, cache L1 per codice e/o dati, predittore dei salti, ecc. ). I quali, manco a dirlo, influenzano i risultati dei benchmark.
Gli utenti non hanno, in ogni caso, di questi problemi filosofici, in quanto hanno la possibilità di controllare i prodotti concreti che sono in vendita (guardando alle miriadi di loro recensioni o effettuando direttamente loro stessi i test) per poi scegliere, quindi, quelli che meglio calzino per loro specifiche esigenze. Problema risolto, almeno per loro!
Confronti sul secondo livello d’astrazione: l’ISA
— Il livello geek
I confronti possibili rimanendo sul piano del secondo livello d’astrazione (l’ISA
) sono relativi alla dimensione complessiva del codice generato, il numero di istruzioni in esso presenti (e, quindi, si può calcolare anche la lunghezza media delle istruzioni), e il numero di riferimenti alla memoria (load e/o store).
Le prestazioni sono fuori questione, perché appartengono al già trattato terzo livello di astrazione (sempre che ciò abbia senso: vedi sopra): sono, infatti, necessari prodotti concreti (anche le simulazioni potrebbero andare bene, se possono riprodurre accuratamente il funzionamento dei chip) per misurarle!
Ma anche qui sorgono altre problematiche. I benchmark, infatti, dipendono dal compilatore usato, la sua versione, quanto bene sia stato sviluppato il backend per la specifica architettura, e quanto ottimizzate siano le librerie built-in / di sistema, tanto per fare alcuni esempi. Bisogna, inoltre, considerare anche l’ABI
utilizzata da compilatore e s.o., perché influenza anch’essa il codice generato.
Infine, bisognerebbe anche considerare quale tipo di codice si volesse generare. Ottimizzato per dimensione? Per velocità? E, in questo caso, avrebbe ancora senso controllare la dimensione degli eseguibili? Se per velocità, a quale livello di ottimizzazione? Usando PGO
? LTO
?
Tutto ciò non considerando minimamente le inerenti differenze delle specifiche microarchitetture (e, quindi, nuovamente di specifici, concreti, prodotti), di cui i compilatori devono assolutamente tenere in considerazione per generare meglio il codice.
Infine bisogna anche considerare che i sorgenti di alcune applicazioni potrebbero anche contenere codice assembly e/o funzioni intrinsic e/o pragma speciali, e tutti questi influenzano, ancora una volta, la qualità del codice generato e/o le prestazioni.
Com’è possibile vedere, ci sono talmente tante possibilità che è difficile arrivare alla classica “ultima parola” sull’argomento. Anche qui, come per il terzo livello di astrazione, tutto dipende da ciò che si vorrebbe misurare e, soprattutto, ottenere. Il tutto continuando a rimanere nell’ambito puramente tecnico / da geek: niente d’interessante per gli utenti…
Con ciò non voglio affermare che bisognerebbe astenersi dall’effettuare confronti, ma soltanto di non pensare di poter arrivare a delle conclusioni lapidarie, pur avendo parecchi dati a disposizione. Un’enorme quantità di benchmark di differenti tipi di applicazioni e che girino su diverse microarchitetture / chip possono essere certamente molto utili per farsi una grossolana idea sul “potenziale” di un’ISA
, senza pretendere, con ciò, di arrivare a una conclusione generale e definitiva.
Anche perché ognuno potrebbe avere la propria, legittima, idea su cosa sarebbe o dovrebbe essere importante in un’ISA
e/o una microarchitettura, e ciò ne influenzerebbe di conseguenza il giudizio (a causa dei preconcetti che ha consolidato).
Confronti sul primo di livello d’astrazione: la macrofamiglia — Il livello filosofo
A questo punto si potrebbe pure pensare che, applicando un ragionamento simile, non sarebbe possibile o sensato effettuare confronti fra CPU / processori RISC
o CISC
. In realtà questa è una storia completamente diversa, in quanto esistono precise definizioni su cosa sia un processore RISC
e, au contraire (perché i concetti sono mutuamente esclusivi), cosa sia un CISC
.
Di questo ne riparleremo nel prossimo articolo, che non soltanto è incentrato proprio sulle loro definizioni, ma rappresenta anche il principale motivo per cui ho scritto questa serie (fare chiarezza sui termini e, quindi, anche sugli altri livelli di astrazione).