Dopo aver discusso della distribuzione e frequenza del numero di operandi, questa volta puntiamo l’attenzione alle modalità d’indirizzamento verso la memoria che sono utilizzate dalle istruzioni dalle architetture x86 e x64.
E’ chiaro che non tutte le istruzioni hanno bisogno di accedere alla memoria, per cui le statistiche riguarderanno soltanto quelle che effettivamente ne faranno uso.
In particolare queste famiglie di microprocessori possono eseguire un solo accesso alla memoria, fatta eccezione per alcune particolari istruzioni “legacy” (quelle che operano sulle cosiddette “stringhe”), che consentono di specificare un operando sorgente (in lettura) e uno destinazione (in scrittura), e in un solo caso (CMPS) addirittura due operandi sorgente (entrambi in lettura).
Sebbene non strettamente legati alla lettura o scrittura di dati da e verso la memoria, sono stati inseriti anche i dati riguardo ai salti relativi.
La beta pubblica di Adobe Photoshop CS6 a 32 bit (PS32) mostra la seguente distribuzione:
Addressing modes: Address mode Count [EBP-DISP8*4] 208695 [REG+DISP8] 187256 PC32 151488 [ESP+DISP8*4] 126268 PC8 100865 [REG] 66870 [DISP32] 42151 [REG+DISP32] 41599 [REG+REG*SC+DISP8] 30736 [EBP-DISP32] 23608 [REG+REG*SC] 20853 [REG+REG*SC+DISP32] 2707 [ESP+DISP32] 78
I più attenti avranno notato subito una strana modalità d’indirizzamento che risulta del tutto inesistente in questa famiglia di processori, e che occupa già la prima posizione. Si tratta di quella con registro base EBP e offset negativo costituito da 8 bit, ma quest’ultimo risulta moltiplicato per quattro: [EBP-DISP8*4].
La ragione di questa scelta è racchiusa negli studi che sto affrontando da circa due anni a questa parte, dunque funzionali al progetto a cui sto lavorando, e di cui al momento non ho intenzione di discutere.
Questi dati, però, sono stati estratti dalle normali istruzioni x86, e dunque vanno collocati da qualche parte. Essi andranno a confluire nella classica modalità [REG+DISP8] e [REG+DISP32], che fanno uso rispettivamente di offset a 8 e 32 bit.
Simile alla precedente, e per la quale valgono le medesime considerazioni, è la modalità [ESP+DISP8*4], che utilizza il registro base ESP, ma questa volta con offset positivo e ancora una volta moltiplicato per quattro.
Com’è possibile vedere dai numeri, non si tratta di una suddivisione arbitraria, ma ben medita, poiché queste due modalità “fittizie” arrivano, assieme, alla somma di tutte le altre modalità d’indirizzamento basate su registro più offset.
La spiegazione è molto semplice, e dipende dall’ABI dell’architettura x86, che prevede un ampio uso dello stack sia per passare i parametri che per le variabili locali, e dunque la formazione di uno stack frame per il loro accesso, che si appoggia al registro EBP o ESP a seconda del tipo di variabile a cui accedere.
L’uso della moltiplicazione per 4 dell’offset deriva, invece, dall’analisi della dimensione tipica dei dati, pari a 4 byte (una double word), e tale risulta pure l’allineamento dell’offset.
Altro elemento importante da non sottovalutare, è il fatto che il registro EBP generalmente utilizza sempre un offset negativo, mentre per ESP è sempre positivo. Ciò è il motivo per cui gli offset sono sempre negativi e positivi rispettivamente, e dunque per massimizzare l’uso degli 8 bit a disposizione, questi vengono impiegati tutti allo scopo, forzando implicitamente il segno da utilizzare.
In ogni caso, e per ritornare più in linea col tema dell’articolo, il quadro che emerge è di un assoluto dominio della modalità d’indirizzamento data da un registro base più un offset rispetto agli altri, ossia quella ben più complicata che aggiunge anche un registro indice scalato ([REG+REG*SC+DISP]) e la più semplice con indirizzamento assoluto ([DISP32]).
Questi risultati non sorprendono, in quanto è ben più raro l’uso di un indice scalato, che in genere è presente quando si manipolano array; operazione, questa, non rara, ma che può incidere molto a livello prestazionale sui processori che non ne siano dotati (è facile che i loop manipolino degli array).
Un’altra doverosa precisazione va fatta per la modalità d’indirizzamento assoluto, [DISP32], che è stata riportata esclusivamente con offset a 32 bit, quando in realtà circa la metà delle istruzioni sfrutta un offset a 8 bit. Anche questa “anomalia” nasce dagli studi effettuati, che al momento non verranno discussi.
Infine e visto che ne sono stati riportati i dati, un accenno va alla modalità d’indirizzamento relativo, rappresentata da PC8 e PC32, che risulta molto frequente, a causa delle numerose istruzioni di salto (JMP, Jcc) e di chiamata a funzione (CALL), con le prime che prediligono offset a 8 bit, mentre le seconde a 32 bit.
Passando alla beta pubblica di Adobe Photoshop CS6 a 64 bit (PS64) lo scenario è, invece, questo:
Addressing modes: Address mode Count [RSP+DISP8*8] 320474 [REG+DISP8] 170953 PC32 168789 PC8 116645 [REG+DISP32] 84105 [REG] 62352 [RIP+DISP32] 58299 [REG+REG*SC+DISP8] 49512 [REG+REG*SC] 33659 [RBP-DISP8*8] 25089 [REG+REG*SC+DISP32] 5544 [RSP+DISP32] 1643 [DISP8] 2
Non si presentano grosse sorprese, tenendo conto di quanto analizzato prima e delle differenze di ABI fra le due architetture, di cui abbiamo già ampiamente discusso nei precedenti articoli.
E’ quasi sparito l’uso della modalità d’indirizzamento che fa uso del registro RBP (EBP nel codice a 32 bit) con offset negativo, poiché non viene creato uno stack frame alla stessa maniera di x86, che si appoggia a questo registro per accedere alle variabili presenti nello stack.
In questo caso con x64 si fa semplicemente spazio nello stack per le variabili locali (incluse quelle usate per conservare temporaneamente i valori dei registri in cui sono stati passati i parametri), e quindi si usa esclusivamente RSP (ESP nel codice a 32 bit), accompagnato da un offset positivo, per indirizzarle.
Anche in questo caso si può notare subito il fatto che gli offset risultino moltiplicati per 8 (in precedenza per 4), per le stesse considerazioni fatte prima riguardo ai miei studi. Adesso, essendo i registri a 64 bit e non a 32, i dati sono, però, costituiti da 8 byte, appunto, e gli offset sono, quindi, allineati per questa quantità.
Per la cronaca, una scelta simile è stata fatta da Intel con l’architettura di Larrabee/Knights Corner (commercializzata come Xeon Phi), ma applicata soltanto all’accesso alla memoria da parte della nuova unità SIMD di cui è dotata.
La motivazione penso sia ormai chiara, ed è dettata dalla necessità di sfruttare al meglio gli 8 bit a disposizione, in modo da ottenere una migliore densità di codice. Infatti senza questa strategia di moltiplicare l’offset per la dimensione dei dati sarebbero stati richiesti molto più spesso offset a 32 bit, che occupano 3 byte in più. Cosa, questa, non da poco, in quanto stressa ancora di più la memoria e la cache TLB, incidendo, in ultima analisi, anche sulle prestazioni.
Tutto ciò risulta particolarmente evidente con x64, dove gli offset tendono a un naturale aumento a causa dei registri che occupano il doppio dello spazio, generalmente nell’uso degli interi e dei puntatori (entrambi a 64 bit, adesso). Anche le statistiche riflettono questo cambiamento, presentando un generale minor uso di offset a 8 bit, e un corrispondente aumento di quelli a 32 bit, com’è possibile notare comparando le rispettive modalità in x86 e x64.
Per quest’ultima architettura si registra, invece, un generale aumento delle modalità che fanno uso, oltre al registro base e all’offset, di un registro indice scalato: circa il 50% in più rispetto a x86, con punte del 100% quando si utilizzano offset a 32 bit. Non sono chiare le motivazioni che hanno portato a questi risultati, per far luce sui quali sarebbe necessario andare ad analizzare i contesti in cui sono state usate nelle istruzioni, ma ciò esula dallo scopo di questa serie di articoli (lo sforzo richiesto, peraltro, sarebbe notevole).
Risulta, poi, quasi sparita la modalità con indirizzamento assoluto, [DISP32], che è stata rimpiazzata da [DISP8] e [RIP+DISP32]. [DISP8] in questo caso viene usata per accedere a variabili di thread (o di kernel, nel caso di codice del s.o.), come vedremo nel pezzo di chiusura della serie, quando discuteremo dei numeri sul “legacy“.
[RIP+DISP32] viene, invece, impiegato per accedere alle variabili globali. Il risultato è simile a x86 (funzionalmente è identico all’uso di [DISP32]), ma con la notevole differenza che in questo caso il codice è relativo (rispetto al program counter: RIP) e non assoluto com’era in precedenza (e che richiede eventualmente un’operazione di rilocazione da parte del loader del s.o. quando carica l’eseguibile).
E’ singolare, invece, che l’uso di [RIP+DISP32] sia aumentato del 38% circa rispetto [DISP32], ma facilmente spiegabile con la diversa ABI adottata in x64 rispetto a x86, con la prima che fa un maggior uso di istruzioni LEA per caricare nei registri i puntatori da passare alle routine da chiamare, anziché istruzioni PUSH con valori immediati (a 32 bit) per lo stesso scopo.
L’uso di PC8 e PC32, infine, è paragonabile a quello di x86, con un leggero aumento di circa il 10% per entrambi.
Il prossimo articolo della serie si focalizzerà sulla distribuzione dei valori immediati (interi) utilizzati in alcune istruzioni.
Scusa ma che senso ha questa frase :
La ragione di questa scelta è racchiusa negli studi che sto affrontando da circa due anni a questa parte, dunque funzionali al progetto a cui sto lavorando, e di cui al momento non ho intenzione di discutere.
Forse era meglio tralasciarla del tutto, non ti pare ?
Mi rendo conto che dal contesto non era chiaro, anche se dopo (quando parlo dello stesso argomento per x64) ho fornito una spiegazione del perché sia meglio utilizzare un offset moltiplicato per un fattore.
Comunque ho tralasciato di discutere il motivo per cui abbia scorporato le frequenze della modalità [REG+DISP] soltanto per i casi di EBP/RSP con offset negativo (e scalato di 4/8) e di ESP/RSP (con offset positivo e scalato di 4/8), facendole divenire voci a sé stanti.
Non ho, però, omesso (né avrebbe avuto senso farlo) il dato di per sé, perché fa parte delle statistiche, e dunque andava mostrato ugualmente, visto che l’argomento dell’articolo è proprio questo.
Spero di essere stato chiaro questa volta.
Credo si chiedesse il perchè citare il fatto che stai lavorando ad un progetto di cui non vuoi parlare ;)
Il fatto è che dovevo giustificare in qualche modo quei risultati che non trovano riscontro con le architetture x86 e x64. Quelli che ho inserito nell’articolo non sono l’output esatto di quanto è stato generato dal mio programma di raccolta delle statistiche; in realtà l’elenco è un altro, che contiene altre informazioni più dettagliate e “sensibili” (per il mio progetto), che ho provveduto ad accorpare opportunamente, facendo rientrare il tutto nella normale sfera delle architetture oggetto degli studi.
Le uniche voci che non ho potuto accorpare sono quelle che ho citato, perché non potevo inserirle in nessuna delle altre (che poi sarebbero [REG+DISP8] e [REG+DISP32]), in quanto confluirebbero in due voci, e non potevo arbitrariamente decidere di spostarle tutto in un solo gruppo (ad esempio [REG+DISP32]), perché avrei falsato le statistiche.
Ho preferito, quindi, lasciarle così, e aggiungere quella frase. Mi rendo conto che non è molto garbato nei confronti dei lettori, ma ho preferito non falsare le statistiche ed essere onesto.
Comunque non è che non voglia parlare del mio progetto. In realtà fra le righe e soprattutto nei commenti tante volte ne parlo (indirettamente). Ma al momento non voglio fornire dettagli su esso.
Per chi ha accesso a Linked-in, c’è una descrizione di massima (molto ad alto livello) nella sezione progetti, alla prima voce (la più recente). Capirete perché al momento non voglio rilasciare informazioni più precise. ;)
Complimenti Cesare per la serie di articoli sui processori X86. Anche se sono un profano è un piacere leggerti.
Grazie. Mi spiace soltanto che siano troppo tecnici, ma non ho idee su come renderli più “potabili”.
Ciao Cesare volevo farti i complimenti per questi studi.
Una domanda ti volevo fare.
Qui tu (scusa se ti do del tu) dimostri che sia in x86 che x64 usando cs6 si nota un miglioramento del 10% solo se non ho capito male.
Io penso che la colpa sia della struttura dello stesso programma e sul sistema operativo su cui gira che non sfrutta a pieno x64 magari in futuro quando verrà sfruttato meglio x64 dal sistema operativo ci saranno miglioramenti anche del 30 o 40%.
Che ne pensi?
Grazie.
Il 10% di cui parlo nell’articolo riguarda l’aumento del numero di salti (e questo potrebbe non giocare a favore di x64, se si trattasse per lo più dei salti condizionati).
Quindi non mi riferivo alla prestazioni, se era questo a cui pensavi. Se potessi essere un po’ più chiaro, ti potrei rispondere meglio.
Grazie della risposta,
questi salti sono causati da cosa?
Un mio pensiero e su una poca compatibilità tra software e hardware
Non so da cosa potrebbe essere dovuto l’aumento dei salti. Dal codice che ho analizzato non ho notato “pattern” o “trend” dai quali si potesse desumere.
Comunque non so esattamente a cosa ti riferisca, ma la “compatibilità” fra software e hardware è necessaria. Altrimenti non funzionerebbe niente, o ci sarebbero seri problemi.
x86 e x64 come architetture sono ormai ben note, e direi abbastanza sfruttate.
Molto più lavoro ci sarà per la neonata ARM64 (ARMv8) che ha un’ISA diversa dalla vecchia ARM (a 32 bit).
Scusa se non c’entra ma visto che nei commenti hai accennato al tuo profilo linkedin sono andato a vederlo e oltre tutte le cose interessantissime che hai fatto e ai tuoi progetti mi ha colpito che c’è scritto che hai fatto le superiori nel 1986 – 1990 quindi le hai fatte in quattro anni? hai fatto due anni in uno o è un errore?
E’ sicuramente un errore, che vado a correggere di corsa. Grazie per avermelo fatto notare! :)
Lieto di averti aiutato, anche se forse avrei preferito fosse vero rendendo il tuo curriculum ancora più straordinario di quanto già non sia.
A scuola sono stato abbastanza bravo in tutte le materie, tranne educazione fisica. Ricordo, ad esempio, che la prof.ssa d’italiano del 4° e 5° leggeva i miei temi in sala professori, perché ne era entusiasta.
Comunque avrei potuto dare di più, ma facevo il minimo indispensabile. Non ho mai studiato seriamente: arrivato a casa, dopo aver mangiato, facevo i compiti scritti, e poi ero fuori a giocare con gli amici (calcetto, tennis, atletica) o in sala giochi; oppure passavo il tempo a casa a guardare cartoni animati, telefilm, film, o a stare al computer a smanettare (quando finalmente ne ho avuto uno).
La scuola era l’ultimo dei miei pensieri. Me la sono potuta cavare perché all’epoca avevo un’ottima memoria, e in genere mi bastava stare attento alle spiegazioni in classe, o sfruttare il viaggio in autobus per ripassare qualcosa, magari prima di una possibile interrogazione (al limite mi svegliavo alle 4 o alle 5 per ripassare prima di essere interrogato, se sentivo di averne bisogno).
Il mio cruccio rimane l’università, dove ho speso troppo tempo lavorando, oppure a progetti miei invece di studiare e laurearmi in tempo. Infatti ho preso il pezzo di carta molto tardi.
Alla fine non so se sia stato un bene o un male, perché la mia esperienza deriva da tutto ciò. Anche da questi miei errori, e dalla voglia di rendermi indipendente dai miei genitori senza pesare sulle loro spalle. Ma se potessi tornare indietro preferirei laurearmi subito, perché per lavorare o smanettare il tempo lo si può trovare benissimo dopo.
Scusate l’OT. A breve dovrei riprendere questa serie e completarla, perché poi non so se mi sarà possibile farlo. Dovrei aggiungere, spero presto, un altro tassello al mio curriculum :), e potrei avere problemi a continuare.
Tranne educazione fisica… Vuoi dimostrare di essere un “nerd puro”?
Sicuro che il tempo lo si può trovare anche dopo? Immagino poi dipenda anche dal lavoro o dai lavori che uno fa. Quindi forse ti sei reso conto che per te era così ma non generalizzerei.
La sala giochi… io sono riuscito ad andarci qualche volta quando ormai erano rare come mosche bianche e non ci andava già più nessuno…
Ho 42 anni, per cui ho visto arrivare i primi cabinati (in bianco e nero, oppure con delle plastiche colorate sovrapposte allo schermo per “simulare” il colore) qui in Italia, e sono stati una componente fondamentale della mia esperienza (infatti per più di 3 anni ho lavorato allo sviluppo di videogiochi per Amiga, tempo fa) e… della mia spensieratezza.
Il tempo libero dopo la laurea e col lavoro lo trovi, anche con la famiglia. Smanettando la sera/notte o la mattina presto, e qualche ora nei fine settimana, sono riuscito a realizzare i progetti che hai avuto modo di vedere nel mio profilo. Il tutto con due bambini e la moglie. Non è facile conciliare tutto, ma si può fare, facendo magari qualche sacrificio.
Per il resto, ho sempre cercato di applicarmi nello sport e nell’educazione fisica, ma il mio rendimento, per quanti sforzi potessi fare, rimaneva nella sufficienza… -_-