|
|
![]() |
|
Strumenti |
![]() |
#101 | ||||||||||
Senior Member
Iscritto dal: Jan 2007
Messaggi: 4127
|
Ed io ho risposto a mia volta.
![]() Secondo te i progettisti di ARM quando hanno costruito da zero il set d'istruzioni Aarch64 non hanno fatto le loro considerazione su quale opzione implementativa era la più performante sia nel presente che sul lungo termine ? E' mostruoso perchè il frontend può fornire abbastanza lavoro a tale backend da rendere conveniente la cosa. Ed io ti ho risposto sotto con vari controesempi di istruzioni decisamente complesse che permettono di risparmiare un fottio di jump condizionali (che pesano molto sulle prestazioni effettive). ;-) Quote:
![]() Con il redesign avvenuto con Aarch64 hanno rifatto tutto da zero, ri-valutando cosa c'era di buono nei set d'istruzioni e nelle architetture già esistenti e soppesando bene cosa tenere e cosa no. Quote:
Peccato che su x86-64 sia sconsigliato usare la vecchia FPU per questioni di prestazioni. Quote:
i = valore immediato di 5 bit per casi in cui basta un incremento immediato CCMN rn, #i5, #f4, cc if(cc) rn + i; else N:Z:C:V = f CCMN rn, rm, #f4, cc if(cc) rn + rm; else N:Z:C:V = f Le CCMN permettono di contare un certo numero di condizioni differenti oppure di selezionare elementi differenti in un array (oppure di selezionare il prossimo elemento mentre si scorre un array, saltando quelli che non soddisfano certe condizioni) CCMP rn, #i5, #f4, cc if(cc) rn − i; else N:Z:C:V = f CCMP rn, rm, #f4, cc if(cc) rn − rm; else N:Z:C:V = f Le CCMP sono la versione "rovesciata" di CCMN. Entrambe tornano anche utili per indicizzare una lookup table (di dati oppure di indirizzi a cui saltare in base a condizioni complesse) in modo da valutare senza jump tutta una sfilza di condizioni ed eseguire un singolo jmp. Hai presente tutti i casi in cui ci sono if..then..else annidati, oppure delle switch .. case .. belle complicate con valori più o meno contigui, intervalli case consecutivi ecc. ? La cosa torna utile decisamente molto spesso mi sembra. CINC rd, rn, cc if(cc) rd = rn + 1; else rd = rn Incremento condizionale, utile per conteggio di eventi e come parte ri codice branchless. CINV rd, rn, cc if(cc) rd = ∼rn; else rd = rn Not condizionale (anche questo utile per codificare codice in forma branchless) + move CNEG rd, rn, cc if(cc) rd = −rn; else rd = rn Negazione condizionale + move CSINC rd, rn, rm, cc if(cc) rd = rn; else rd = rm + 1 Questa torna utile per aggiornare puntatori o indici su un ring buffer senza essere costretti ad usare array o buffer con numero di elementi che corrisponde a potenze di due. Oppure per scorrere liste allocate in spezzoni di elementi consecutivi. CSINV rd, rn, rm, cc if(cc) rd = rn; else rd = ∼rm CSNEG rd, rn, rm, cc if(cc) rd = rn; else rd = −rm Anche queste tornano utile per convertire codice che normalmente contiene dei branch in una più rapida e leggera versione branchless. Quote:
E' roba che serve ad esempio per codificare espressioni booleane associate ad eventi o condizioni in stringhe di fino a 64 bit ed elaborarle tutte insieme con singole istruzioni. E' roba che a livello di codice sorgente non vedi direttamente ma che il compilatore (o uno sviluppatore che deve spremere ogni singolo ciclo utile da un processore) usa per generare codice branchless. Quote:
![]() Inoltre quegli shift tornano utili per implementare forme di calcolo indirizzi molto più complesse di quello che può fare una LEA e pre-caricarle su registri. In loop particolarmente critici, quando hai 32 registri invece di 16 puoi permetterti di pre-caricarne 16 con costanti ed ottenere prestazioni migliori di quel che può fare un x86 con 16 registri ed operandi in memoria. Quote:
Quote:
Quote:
![]() Quote:
Ed a quel punto è stata fatta di necessità virtù. Quote:
Non hanno un backend con così tante risorse perchè per un x86 gli "sweet spot" tra parallelismo nel backend, complessità del decoder x86, frequenza massima raggiungibile, pressione su registri e unità di load/store ecc. sono differenti (e nel caso di Intel, ulteriormente limitati dall'aver necessità di fare i backporting verso i 14nm). |
||||||||||
![]() |
![]() |
![]() |
#102 | |||||||
Senior Member
Iscritto dal: Jan 2002
Città: Germania
Messaggi: 25142
|
Quote:
Ad esempio, l'HyperThreading l'ha tirato fuori proprio con questi processori. E dopo QUINDICI anni AMD ha pensato di integrarlo nei suoi processori: perché ha dormito tutto questo tempo? Ma dopo il P4 potrei citarti Centrino/Banias, i Core, i Core Duo, Larrabee, gli Xeon Phi. Le AVX 1&2, le FMA, le AVX-512. Di innovazione direi che ne abbia tirata fuori abbastanza. Quote:
Quote:
E l'ha fatto pure quando la sua superiorità era schiacciante. Ti ho già elencato i processori che erano già in cantiere da ben prima che AMD se ne uscisse fuori con Zen/Ryzen. Anzi, da quando non si sapeva proprio nulla di questi processori, a parte che AMD ci stava lavorando. Quote:
Quote:
Quote:
Quote:
![]()
__________________
Per iniziare a programmare c'è solo Python con questo o quest'altro (più avanzato) libro @LinkedIn Non parlo in alcun modo a nome dell'azienda per la quale lavoro Ho poco tempo per frequentare il forum; eventualmente, contattatemi in PVT. Fanboys |
|||||||
![]() |
![]() |
![]() |
#103 | ||||||||||||||||||||||
Senior Member
Iscritto dal: Jan 2002
Città: Germania
Messaggi: 25142
|
Quote:
Quote:
Ma è anche vero che il backend dei processori x86/x64 sia sottodimensionato rispetto al frontend. In particolare quello di Intel, perché AMD già con la prima versione di Zen ha messo sul piatto più risorse sul backend rispetto a Intel, e le ha via via aumentate con Zen2 e Zen3. Non è un caso che proprio Zen3 riesca a tenere testa all'M1 di Apple. Eppure il frontend è rimasto sostanzialmente lo stesso, e qui mi riferisco in particolare ai decoder, che continuano a decodificare al massimo 4 istruzioni per ciclo di clock (5 o 6 col macro-op fusion, almeno per Intel, come ho riportato nell'altro thread). Quote:
Quote:
![]() Quote:
![]() Quote:
Quote:
Per cui quell'istruzione può essere utile. E dico "può" perché il codice FP è più lineare e meno soggetto a salti, rispetto a quello "intero" / "scalare", per cui istruzioni del genere vengono usate molto meno. Quote:
Quote:
Ma si può fare di gran lunga meglio (nei casi più comuni), senza nessuna catena di istruzioni (confronti, accumuli) del genere. E si può fare già adesso sfruttando un certo numero fisso di istruzioni (una manciata. Quindi non molte). Per la precisione: di opportune istruzioni. Con una singola istruzione (o forse un paio, per non renderla troppo complicata, e quindi di difficile implementazione a livello micro-architetturale) sarebbe anche meglio, ovviamente. Quote:
Quote:
Quote:
Quote:
Che siano branchless è ovvio, ma non mi dice niente. Quote:
Lo shift può essere utile per spostare maschere di bit nella giusta posizione, per poi effettuare l'operazione. Questo può servire, senz'altro. Quote:
![]() Quote:
Alla memoria si accede molto spesso secondo pattern precisi, e in particolare indicizzando array per accedere a determinati elementi. Questi sono i casi comuni, e sono ampiamente coperti dalla LEA. Quote:
Quote:
Per questo è un'istruzione molto usata. Infatti ce l'ha pure Motorola nei 68K, a cui ha pure aggiunto l'utilissima PEA (Push Effective Address; anche qui per il passaggio di indirizzi nelle chiamate a funzione. Istruzione che ho mutuato nella mia architettura, e che ha contribuito a un netto miglioramento nella densità di codice e nella diminuzione delle istruzioni eseguite ![]() Quote:
Quote:
Quote:
Si tratta di decisioni micro-architetturali che sono frutto della visione che hanno gli ingegneri su come e quali risorse mettere nel backend. Infatti gli ingegneri di AMD hanno avuto una visione diversa, e hanno preferito puntare di più sulle ALU. Giusto per fare un altro esempio. Quote:
__________________
Per iniziare a programmare c'è solo Python con questo o quest'altro (più avanzato) libro @LinkedIn Non parlo in alcun modo a nome dell'azienda per la quale lavoro Ho poco tempo per frequentare il forum; eventualmente, contattatemi in PVT. Fanboys |
||||||||||||||||||||||
![]() |
![]() |
![]() |
#104 | ||
Senior Member
Iscritto dal: Jan 2007
Messaggi: 4127
|
Quote:
Tra le tante possibilità c'e questa: rd = valore; if (cc) rd = ∼rd; ed analogamente per CNEG rd, rn, cc if(cc) rd = −rn; else rd = rn uno dei possibili utilizzi è questo: rd = valore; if (cc) rd = -rd; In entrambi i casi si elimina un caso relativamente frequente di branch condizionale che spesso ha circa un 50% di probabilità di essere eseguito e due move con interdipendenze, trasformando il tutto in una singola istruzione. Quote:
CSNEG rd, rn, rm, cc if(cc) rd = rn; else rd = −rm In entrambi i casi con DUE registri puoi codificare SEI sottoespressioni (rn, ∼rn, -rn, rm, ∼rm, -rm) che tornano utili per eseguire espressioni condizionali senza branch in cui si deve scegliere tra il risultato di due subespressioni in cui una di esse ha come operazione finale la negazione o il not binario. E' una cosa basata sull'analisi delle espressioni condizionali nei sorgenti e loro ottimizzazioni. |
||
![]() |
![]() |
![]() |
#105 |
Senior Member
Iscritto dal: Jan 2002
Città: Germania
Messaggi: 25142
|
Scusami, forse non sono stato chiaro.
Come funzionino di per sé le istruzioni mi è chiaro e semplice: è in quali ambiti applicativi (in quali parti di qualche algoritmo noto) che non è facile capire come passano essere impiegate. Con le altre istruzioni avevi fatto degli esempi di codice reale, che mancano su queste. Tutto qui. Ma se non ti viene in mente niente va bene lo stesso: la mia era pura curiosità. ![]()
__________________
Per iniziare a programmare c'è solo Python con questo o quest'altro (più avanzato) libro @LinkedIn Non parlo in alcun modo a nome dell'azienda per la quale lavoro Ho poco tempo per frequentare il forum; eventualmente, contattatemi in PVT. Fanboys |
![]() |
![]() |
![]() |
Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 07:43.