di  -  giovedì 14 marzo 2013

La lunga serie sull’analisi dell’aspetto legacy” di x86 e x64 si era conclusa con l’annuncio (nei commenti) di una nuova serie di articoli che avrebbero trattato queste due ISA dal punto di statistico; è arrivato il momento di mantenere fede alle promesse.

Esistono già numerosi articoli e pubblicazioni scientifiche che hanno affrontato l’argomento, ma quelli che seguiranno sono frutto di particolari studi effettuati su diversi aspetti delle istruzioni di queste architetture dal punto di vista quantitativo, ma che offrono spazio anche ad alcune riflessioni.

Parlo non a caso di architetture, distinguendo quindi nettamente x86 e x64, perché, pur essendo quest’ultima un’estensione della prima, le differenze sono notevoli non soltanto dal punto di vista dell’ISA (x64 ha raddoppiato il numero di registri, eliminato alcune istruzioni legacy, e introdotto una nuova modalità d’indirizzamento che fa uso dell’IP / PC), ma anche riguardo alla tipologia di codice effettivamente eseguito per svolgere gli stessi compiti.

In realtà è bene precisare che il codice prodotto ed eseguito dipende molto anche dall’ABI utilizzata, quindi dall’uso dei registri “imposto” dal sistema operativo su cui sta girando, e in ultima analisi anche da quello del compilatore, che nella produzione di codice può, ad esempio, riservare alcuni registri per particolari funzionalità.

Non ci occuperemo di questi due aspetti, se non tenendo conto che gli eseguibili utilizzati per estrapolare le informazioni sono stati generati per il s.o. Windows nelle incarnazioni a 32 (x86) e 64 (x64) bit, e dunque trattasi di file EXE o DLL che utilizzano il famoso formato PE che trae origini dal vecchio DOS, ma che col tempo è stato esteso per supportare anche architetture ben diverse da quelle di Intel e AMD, come pure dell’IL impiegato in .NET.

In particolare sono stati utilizzati gli eseguibili della beta pubblica di Adobe Photoshop CS6, che nell’archivio si trovano come file senza estensione nominati _75_777015c53e2f5a702d01589cd0020445 e _73_7d4efa378b68bcee72e36ede90f7907d, rispettivamente per la versione a 32 e 64 bit dell’applicazione, che successivamente verranno indicati come PS32 e PS64.

Gli studi hanno coinvolto l’analisi anche di diversi altri eseguibili (ad esempio il MAME, Firebird SQL, MySQL, VirtualBox, ecc., come pure il driver OpenGL di AMD/Ati), ma questi due sono stati privilegiati perché hanno consentito di estrapolare molte più istruzioni, consentendo, quindi, di avere delle statistiche molte più estese.

Per disassemblare gli eseguibili, per poi raccoglierne tutte le statistiche, è stata utilizza diStorm3, una comoda libreria, di cui esistono binding per Python, che mette a disposizione nella sezione download un’applicazione (dislib.py) Python per disassemblare proprio gli eseguibili PE.

dislib.py si limita a disassemblare al massimo 4KB di codice partendo dall’entry point (indirizzo della prima istruzione eseguita), per cui è stata pesantemente modificata per arrivare a coprire / scovare quanto più codice possibile, senza però andare a disassemblare ciecamente zone del file che potrebbero essere dati, i quali avrebbero falsato le informazioni raccolte.

In questo modo si è ottenuto un piccolo insieme di tutte le istruzioni presenti nell’eseguibile, ma di buona qualità (niente spazzatura, in sostanza) per gli scopi che erano stati fissati, e cioè raccogliere dati utili a comprendere la natura e la conformazione delle varie istruzioni, a cui si aggiungono altre finalità che non riguardano squisitamente le statistiche.

Sono state analizzate circa 1,75 milioni di istruzioni (1.746.569 per la precisione) per PS32, e circa 1,74 milioni (1.737.331) per PS64. Non sono, per ovvie ragioni, un campione rappresentativo dell’intera tipologia di codice e istruzioni che si trovano nelle più disparate applicazioni o giochi, ma vengono prese esclusivamente per presentare dei dati (da qualcosa si dovrà pur partire) su cosa si può trovare in un eseguibile x86 o x64.

Nel primo caso lo spazio occupato dalle istruzioni è pari a circa 5,63MB (5.634.556 byte esattamente), mentre nel secondo è di 7,56MB (7.556.180 byte). Si tratta del primo dato interessante poiché, assieme al numero di istruzioni, ci fornisce una misura della dimensione media del codice, che è pari a circa 3,2 byte e 4,3 byte, rispettivamente, per istruzione.

Il codice a 32 bit appare, dunque, generalmente più denso rispetto a quello a 64 bit, che risulta penalizzato in qualche misura. Ciò dipende da due fattori, essenzialmente: l’uso del prefisso REX (usato per accedere ai nuovi 8 registri che sono stati aggiunti all’ISA, come pure per specificare che si stanno manipolando 64 bit e non 32) e la diversa ABI adottata (nel codice a 32 bit si esegue il push sullo stack dei parametri da passare a una funzione, mentre in quello a 64 bit si caricano valori sui registri), come vedremo in un prossimo articolo che metterà anche a confronto alcuni spezzoni di codice.

Le istruzioni sono state poi divise in macrofamiglie: INTEGER, FPU, MMX, 3DNOW, SSE, AVX, che indicano a quale unità funzionale appartengono con l’eccezione di INTEGER, che non si riferisce soltanto all’ALU per i calcoli interi, ma che si fa carico di tutte le istruzioni che appartengono al “core” del processore (quindi anche di salti, segmenti, istruzioni legacy varie, virtual machine, ecc.).

A queste è stata aggiunta anche AVXFAKE, che serve esclusivamente a comprendere quanto spazio occuperebbe un’istruzione SSE se fosse codificata “così com’è” nel nuovo set di istruzioni AVX che è stato recentemente aggiunto da Intel alla sua famiglia di processori, con lo scopo non soltanto di estendere il set d’istruzioni (ad esempio dando la possibilità di specificare un terzo o quarto registro con cui operare), ma di ottenere anche una (de)codifica più semplice e, in ultima istanza, compatta (ma solo con codice appositamente generato, che è in grado di sfruttarne meglio le caratteristiche).

Di seguito sono riportati i risultati per PS32:

Class        Count       % Avg sz
INTEGER    1631136   93.39    3.2
FPU         114521    6.56    3.2
SSE            912    0.05    4.0
AVXFAKE        912    0.05    5.0

e per PS64:

Class        Count       % Avg sz
INTEGER    1638505   94.31    4.3
SSE          93942    5.41    5.2
AVXFAKE      93942    5.41    5.3
FPU           4884    0.28    3.1

Com’è possibile notare, la stragrande maggioranza è costituita da istruzioni che ricadono nel dominio del “core” della CPU, e che quindi alla fine determina anche la densità dell’intero codice.

Ciò, però, non ci consente di esprimere valutazioni sulle prestazioni, che sono determinate mediamente per il 90% dai cicli eseguiti, e in questi cicli si può fare pesante uso di istruzioni SSE, tanto per fare un esempio. Inoltre è bene ricordare che quello utilizzato è soltanto un campione che non è rappresentativo né della totalità delle tipologie di codice né dell’intera applicazione a cui appartengono, come già detto.

Un altro dato che emerge è che nel codice a 32 bit sono presenti poche istruzioni SSE, mentre molte sono quelle che appartengono alla vecchia FPU x87. Viceversa, il codice a 64 bit predilige le SSE a discapito dell’FPU. Il tutto sempre tenendo conto dei limiti dell’analisi esposti.

Infine un commento lo merita anche il confronto fra il codice SSE e l’equivalente AVXFAKE, visto che è stato introdotto appositamente. Nel caso di codice a 32 bit le istruzioni SSE presentano una densità di codice migliore rispetto alla codifica AVX, mentre per il codice a 64 bit la densità è sostanzialmente equivalente.

Il motivo è presto spiegato: nel codice a 64 bit si fa uso del famigerato prefisso REX per accedere ai nuovi 8 registri SSE, mentre con la codifica AVX ciò non è necessario nella maggior parte dei casi, in quanto una buona parte delle funzionalità di REX sono assorbite / integrate nei prefissi utilizzati da AVX, oltre al fatto che AVX integra pure i prefissi 66, F2 e F3, come già spiegato in un precedente articolo.

Considerato che il codice a 64 bit è destinato a dominare nel futuro, e che AVX porta con sé altre innovazioni (come ad esempio istruzioni con 3 o 4 operandi, oppure le nuove istruzioni di gather/scatter), risulta evidente che il codice AVX risulterà mediamente più denso rispetto al vecchio SSE.

Nel prossimo articolo continueremo l’analisi mostrando la distribuzione delle istruzioni in base alla loro dimensione, e poi in base al numero di operandi.

11 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    Sasha
     scrive: 

    Complimenti sinceri per questa serie di articoli…
    Devo ammettere che hai fatto crescere interesse in me per qualcosa che ho sempre snobbato, avendo sempre preferito esclusivamente discussioni sulle architetture lato hardware… Poi ogni articolo è ben organizzato ed esaustivo, mi piace! Sinceramente, di nuovo complimenti!

    Tra l’altro, non sapevo ci fossero queste differenze tra x86 e x64…
    Ma questi test li hai realizzati tu? Se si, quanto tempo ti hanno occupato?

  • # 2
    Cesare Di Mauro (Autore del post)
     scrive: 

    Sì, li ho realizzati da solo, ma fanno parte di studi e di un progetto/idea che porto avanti ormai da un anno e mezzo.

    Diciamo che sto riportando una parte dei dati di questi studi, funzionali al tema della serie di articoli. O:-)

    Grazie per i complimenti. :)

  • # 3
    Sisko214
     scrive: 

    Complimenti per gli articoli, che sono di grande levatura tecnica…
    Volevo porre una domanda a Cesare di Mauro, e spero mi voglia perdonare per la domanda stupida che stò per porre.
    Da hobbysta di microcontrollori, quale sono, mi sono sempre chiesto se per queste super-cpu, vengono realizzate applicazioni embedded e vengono realizzati i programmi direttamente in assembler… mi riferisco ad interi applicativi come, in parecchi casi, si fà ancora con i microcontrollori e non i vari pezzi in binario per le HAL dei sistemi operativi.
    Oppure sono così complicati che per forza di cose serve un linguaggio ad alto livello e un compilatore ?
    Grazie.

  • # 4
    Cesare Di Mauro (Autore del post)
     scrive: 

    Diamoci pure del tu. :)

    Dipende tutto dal tipo di microcontroller e dal progetto. Alcuni hanno un’architettura così semplice, che si possono programmare in assembly. Ovviamente ci gireranno progetti abbastanza semplici e gestibili interamente in assembly.
    Per architetture più complicate si usa da tempo il C, e sta prendendo piede anche il C++. L’assembly si usa difficilmente, ma è possibile che vi si possa ricorrere per porzioni di codice critiche, se il progetto richiede certe prestazioni.

  • # 5
    Sisko214
     scrive: 

    Salve Cesare,
    Forse ho posto male la domanda… non mi riferivo ai Pic, Atmel o Arm… intendevo proprio gli intel x32 e x64 multicore….
    Volevo sapere se gli intel (non Atom e compagnia bella) vengono usate anche per progetti verticali (come nei microcontrollori) e quindi vengono programmati in assembler, oppure se sono utilizzati solamente come cpu per personal computer.
    Grazie.

  • # 6
    Cesare Di Mauro (Autore del post)
     scrive: 

    So che esistono delle versioni embedded, tipo l’80376 ( http://www.cpu-world.com/CPUs/80376/index.html ), ma francamente non ho idea se esistano altre soluzioni embedded basate su core più moderni per svolgere compiti da microcontroller o simili.

    Certamente sulla carta è possibile farlo, ma in questi ambiti architetture come PowerPC, ARM, e MIPS dominano perché i loro core sono estremamente ridotti, quindi richiedono meno spazio / transistor, scaldano meno, e sono più facilmente customizzabili.

    E’ un settore in cui non vedo bene Intel con x86 o x64, anche se con Atom sta provando ad attaccare ARM su smartphone & tablet, prima di dominio (quasi) assoluto di quest’ultima.

    Comunque x86 e x64 sono ISA complesse, per cui valgono le stesse considerazioni che ho fatto prima, nel caso in cui queste CPU dovessero venire usate come microcontrollori.

  • # 7
    Mauro
     scrive: 

    Complimenti!
    Articolo che mi ha interessato molto e mi offre alcuni spunti di riflessione. Grazie.

  • # 8
    Filippo
     scrive: 

    Ma ricordo male o il codice legacy x87 non è supportato quando le CPU x86 eseguono codice a 64 bit? Come mai è presente codice “FPU” in un eseguibile x64?

  • # 9
    Cesare Di Mauro (Autore del post)
     scrive: 

    E’ supportato. :) E può trarre vantaggio dall’address space a 64 bit e dalla modalità d’indirizzamento relativa all’IP (RIP).

    Diciamo che è deprecato: viene caldamente suggerito di utilizzare soltanto le SSE2+ (nemmeno le MMX: anche queste sono deprecate, sebbene ancora supportate).

  • # 10
    Guido
     scrive: 

    Mi sembra di ricordare che il motorola 68000 avesse l’indirizzamento relativo ad IP già da subito. Sembra che in Intel (anzi AMD che ha sviluppato x64) scoprano le cose 30 anni dopo i concorrenti!
    Articolo molto interessante.

  • # 11
    Cesare Di Mauro (Autore del post)
     scrive: 

    Grazie. Sì, il 68000 aveva l’indirizzamento relativo al PC, ed era pure possibile usarlo come registro base a cui aggiungere anche un registro dati come indice (e il classico offset).
    L’unico limite era dovuto al fatto che non si potesse usare queste modalità (perché erano due: con offset, oppure con offset + registro indice + scaling dell’indice per 68020+) per le operazioni di scrittura, ma soltanto per quelle di lettura, perché l’idea di Motorola era quella che nell’area marcata come codice non si potesse scrivere, ma soltanto leggere.

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.