Doppio punto a virgola mobile esteso (80-bit) in x87, non SSE2 – non ci manca?

Stavo leggendo oggi sui ricercatori che hanno scoperto che le librerie Phys-X di NVidia utilizzano x87 FP rispetto a SSE2 . Ovviamente questo non sarà ottimale per i set di dati paralleli in cui la velocità supera la precisione. Tuttavia, l’autore dell’articolo continua citando:

Intel iniziò a scoraggiare l’uso di x87 con l’introduzione del P4 alla fine del 2000. AMD deprecato x87 dal K8 nel 2003, poiché x86-64 è definito con il supporto SSE2; Il C7 di VIA ha supportato SSE2 dal 2005. Nelle versioni a 64 bit di Windows, x87 è deprecato per la modalità utente e proibito interamente in modalità kernel. Praticamente tutti gli operatori del settore hanno raccomandato SSE su x87 dal 2005 e non ci sono motivi per utilizzare x87, a meno che il software non debba essere eseguito su un Pentium o 486 incorporato.

Mi sono interrogato su questo. So che x87 usa internamente i doppi estesi a 80 bit per calcolare i valori, e SSE2 no. Questo non importa a nessuno? Mi sembra sorprendente. So che quando eseguo calcoli su punti, linee e poligoni in un piano, i valori possono essere sorprendentemente errati quando si eseguono le sottrazioni, e le aree possono collassare e le linee si susseguono l’un l’altro a causa della mancanza di precisione. I valori a 80 bit rispetto ai valori a 64 bit potrebbero aiutare, immagino.

È sbagliato? In caso contrario, cosa possiamo usare per eseguire doppie operazioni FP estese se x87 è in fase di eliminazione?

Il problema più grande con x87 è fondamentalmente che tutte le operazioni di registrazione vengono eseguite a 80 bit, mentre la maggior parte delle volte le persone usano solo float a 64 bit (cioè float a precisione doppia). Quello che succede è che si carica un float a 64 bit nello stack x87 e questo viene convertito in 80 bit. Fate alcune operazioni su di esso a 80 bit, quindi memorizzatele in memoria, convertendole in 64 bit. Otterrai un risultato diverso rispetto al fatto che se avessi fatto tutte le operazioni con soli 64 bit e con un compilatore ottimizzante potrebbe essere molto imprevedibile il numero di conversioni che un valore potrebbe attraversare, quindi è difficile verificare che stai ricevendo il ” “risposta corretta quando si eseguono i test di regressione.

L’altro problema, che interessa solo dal punto di vista di qualcuno che scrive assembly (o indirettamente scrivendo assembly, nel caso di qualcuno che scrive un generatore di codice per un compilatore), è che x87 usa uno stack di registro, mentre SSE usa individualmente accessibile registri. Con x87 ci sono un sacco di istruzioni aggiuntive per manipolare lo stack, e immagino che Intel e AMD preferiscano far funzionare i loro processori velocemente con il codice SSE piuttosto che cercare di rendere veloci le istruzioni extra x87 di manipolazione dello stack.

Se hai problemi con l’imprecisione, ti consigliamo di dare un’occhiata all’articolo ” Quello che ogni programmatore dovrebbe sapere sull’aritmetica in virgola mobile “, e poi magari usare una libreria matematica di precisione arbitraria (ad es. GMP).

Per fare un uso corretto della matematica a precisione estesa, è necessario che un linguaggio supporti un tipo che può essere utilizzato per memorizzare il risultato di calcoli intermedi e può essere sostituito dalle espressioni che producono tali risultati. Quindi, dato:

void print_dist_squared(double x1, double y1, double x2, double y2) { printf("%12.6f", (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); } 

dovrebbe esserci un tipo che potrebbe essere usato per catturare e sostituire le sottoespressioni comuni x2-x1 e y2-y1 , permettendo di riscrivere il codice come:

 void print_dist_squared(double x1, double y1, double x2, double y2) { some_type dx = x2-x1; some_type dy = y2-y1; printf("%12.6f", dx*dx + dy*dy); } 

senza alterare la semantica del programma. Sfortunatamente, ANSI C non è riuscito a specificare alcun tipo che potesse essere usato per some_type su piattaforms che eseguono calcoli a precisione estesa, ed è diventato molto più comune incolpare Intel per l’esistenza di tipi a precisione estesa piuttosto che incolpare il supporto fallito di ANSI.

In effetti, i tipi a precisione estesa hanno altrettanto valore sulle piattaforms senza unità a virgola mobile come fanno sui processori x87, poiché su tali processori un calcolo come x + y + z comporterebbe le seguenti fasi:

  1. Disimballare la mantissa, l’esponente e possibilmente il segno di x in registri separati (l’esponente e il segno possono spesso essere “a doppia cuccetta”)
  2. Disimballate anche voi.
  3. Spostare a destra la mantissa del valore con l’esponente inferiore, se presente, quindi aggiungere o sottrarre i valori.
  4. Nel caso in cui x e y avessero segni diversi, spostate a sinistra la mantissa finché il bit più a sinistra è 1 e regolate l’esponente in modo appropriato.
  5. Imballare l’esponente e la mantissa in doppio formato.
  6. Disimballare il risultato temporaneo.
  7. Disimballare z.
  8. Spostare a destra la mantissa del valore con l’esponente inferiore, se presente, quindi aggiungere o sottrarre i valori.
  9. Nel caso in cui i risultati precedenti e z avessero segni diversi, spostate a sinistra la mantissa finché il bit più a sinistra è 1 e regolate l’esponente in modo appropriato.
  10. Imballare l’esponente e la mantissa in doppio formato.

L’uso di un tipo a precisione estesa consente di eliminare i passaggi 4, 5 e 6. Poiché una mantissa a 53 bit è troppo grande per essere inserita in meno di quattro registri a 16 bit o due registri a 32 bit, l’esecuzione di un’aggiunta con una mantissa a 64 bit non è più lenta rispetto all’utilizzo di una mantissa a 53 bit, quindi utilizzando matematica di precisione estesa offre calcoli più rapidi senza downside in un linguaggio che supporta un tipo corretto per contenere risultati temporanei . Non c’è motivo di criticare Intel per aver fornito una FPU in grado di eseguire matematica a virgola mobile nel modo che era anche il metodo più efficiente su chip non FPU.

L’altra risposta sembra suggerire che l’uso della precisione a 80 bit sia una ctriggers idea, ma non lo è. Svolge un ruolo talvolta vitale nel tenere a bada l’imprecisione, vedi ad esempio gli scritti di W. Kahan.

Usa sempre l’aritmetica intermedia a 80 bit se riesci a farla franca in velocità. Se ciò significa che devi usare x87 math, beh, fallo. Il supporto per questo è onnipresente e finché le persone continuano a fare la cosa giusta, rimarrà onnipresente.

La doppia precissione è 11 bit inferiore a f80 (circa 2,5 nibble / cifre), per molte app (per lo più giochi) non farebbe male. Ma avrai bisogno di tutta la precisione disponibile per dire, programma spaziale o app medica.

È un po ‘fuorviante quando alcuni dicono che f80 (e scoraggiato da esso) opera in pila. Registri FPU e operazioni simili allo stack operation, forse ciò che rende le persone confuse. In realtà è basato sulla memoria (carica / archivia), non stack per-se, rispetto, ad esempio, alle convenzioni di chiamata come cdecl stdcall che in realtà passano i parametri tramite stack. e niente di sbagliato in questo.

Il grande vantaggio di SSE è in realtà l’operazione di serializzazione, 2, 4, 8 valori contemporaneamente, con molte operazioni di variante. Sì, puoi trasferirli direttamente al registro, ma alla fine li trasferirai comunque alla memoria.

Il grosso svantaggio di f80 è, con una lunghezza di 10 byte dispari, che interrompe l’allineamento. dovresti allinearli 16 per un accesso più veloce. ma non veramente praticabile per array.

Devi ancora usare fpu per le operazioni matematiche trigonometriche e altre trancedental. Per Asm, ci sono molti trucchi f80 davvero divertenti e utili.

Per i giochi e l’app semplice normale (quasi tutti), puoi semplicemente usare il doppio senza far morire qualcuno. Ma per qualche app seria, matematica o scientifica non puoi abbandonare F80.