View Single Post
Old 19-12-2020, 06:52   #103
cdimauro
Senior Member
 
L'Avatar di cdimauro
 
Iscritto dal: Jan 2002
Città: Germania
Messaggi: 26107
Quote:
Originariamente inviato da LMCH Guarda i messaggi
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 ?
Le hanno fatte certamente. E col vantaggio di avere le mani abbastanza libere, perché sapevano già che AArch64 sarebbe dovuta essere non-retrocompatibile con ARM/Thumb.
Quote:
E' mostruoso perchè il frontend può fornire abbastanza lavoro a tale backend da rendere conveniente la cosa.
Beh, è anche normale che sia così, perché è un RISC, come già detto.

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:
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). ;-)
Nulla da dire su questo: sono aggiunte utili ambito prestazionale (come pure sulla densità di codice).
Quote:
Ti ricordo che il set d'istruzioni dei primi ARM aveva già quasi TUTTE le istruzioni ad esecuzione condizionale ben prima degli x86.
Appunto: lo erano TUTTE, e non soltanto alcune accuratamente selezionate, come ha fatto Intel.
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.
Difatto copiando il modello Intel: soltanto alcune istruzioni condizionali, per i casi realmente più frequenti / utili.
Quote:
ARM1 il 5 aprile 1985 l'aveva già implementata in hardware.
Vedi sopra: non è la stessa cosa. Qui parliamo di alcune istruzioni condizionali.
Quote:
Peccato che su x86-64 sia sconsigliato usare la vecchia FPU per questioni di prestazioni.
Ma viene ancora usato quando è richiesta più precisione rispetto a quella normalmente a disposizione con quella doppia.

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:
f = valore immediato di 4 bit che modifica i flag N,Z,C,V
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)
OK, questa era la spiegazione che cercavo: i casi d'uso nel codice reale.
Quote:
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.
Sì. Utile e comune.

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:
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.
OK
Quote:
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
Qui non elenchi casi d'uso, e non mi sembrano molto utili.
Quote:
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.
Utile, senz'altro.
Quote:
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.
Senza casi d'uso è difficile comprenderne l'utilità.

Che siano branchless è ovvio, ma non mi dice niente.
Quote:
No, x86 così proprio non le ha, nota che il secondo operando è opzionalmente shiftabile da 1 a 64bit.
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.
Continuo a non trovare utilità nelle ORN e XORN. Per codificare espressioni booleane sono sufficienti AND, OR, XOR, e ANDN.

Lo shift può essere utile per spostare maschere di bit nella giusta posizione, per poi effettuare l'operazione. Questo può servire, senz'altro.
Quote:
Le scelte fatte da ARM riguardo la dimensione max dei valori immediata deriva da analisi che fanno da decenni sulle caratteristiche del codice sorgente di un sacco di applicazioni, non lo dimenticare.
Lo so bene. E dovresti sapere bene anche tu che per ARM è stata una scelta obbligata, visto che le sue istruzioni non possono caricare costanti arbitrarie, come invece fanno x86/x64.
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.
Saranno casi molto particolare, perché più che scalare l'indice per accedere a un array è difficile utilizzo.

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:
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.
Ne dubito, visto che x64 non richiede alcun registro proprio perché le costanti le può specificare direttamente. Quindi parte a razzo con l'esecuzione del loop. E una volta in esecuzione nel loop la cache non viene nemmeno toccata, e usa direttamente le uop.
Quote:
E' roba che sugli x86 è necessaria perchè fa parte delle "feature" ereditate dagli 8086 ed a suo tempo abbandonarla avrebbe causato seri problemi di prestazioni con le montagne di codice precompilato già in circolazione e codificato per 8086, 80286 ed 80386+.
Ed a quel punto è stata fatta di necessità virtù.
Direi di no: la LEA è oggettivamente utile non soltanto per eseguire somme quaternarie, ma perché una cosa molto comune nel codice è, per l'appunto, il calcolo di indirizzi (è per questo che è nata: Load Effective Address). In particolare se devi passare degli indirizzi a una funzione.

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:
Non è che la macro-op fusion sia un esclusiva degli x86, viene suggerita anche per le implementazioni "ad alte prestazioni" di Risc V.
Sì, lo so. E viene fatto per compensare alle mancanze dell'ISA, che difetta di utili istruzioni, producendo un netto calo prestazionale, come ho già avuto modo di riportare.
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,
Su questo ho parzialmente risposto sopra. Vedremo se in futuro il frontend verrà aumentato (più di 4 istruzioni x86/x64 decodificate).
Quote:
pressione su registri e unità di load/store ecc. sono differenti
No, questo non c'entra. Infatti puoi vedere tu stesso che già coi Core Duo ha posto particolare enfasi alle unità di load/store delle sue micro-architetture. Lo si nota proprio a occhio, confrontando i diagrammi con la concorrenza, che Intel privilegia da sempre questa parte del backend. Che, infatti, ha ulteriormente potenziato con SunnyCove e successori.

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:
(e nel caso di Intel, ulteriormente limitati dall'aver necessità di fare i backporting verso i 14nm).
Hanno ridotto soltanto il numero di massimo: 8 anziché gli attuali 10 del top di gamma. Ma rimangono i core ad alte prestazioni di Sunny Cove.
__________________
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 o nel mio sito. Fanboys
cdimauro è offline   Rispondi citando il messaggio o parte di esso
 
1