Cosa fa realmente la matematica veloce di gcc?


144

Capisco che la --ffast-mathbandiera di gcc può aumentare notevolmente la velocità per le operazioni mobili e va oltre gli standard IEEE, ma non riesco a trovare informazioni su ciò che sta realmente accadendo quando è acceso. Qualcuno può spiegare alcuni dettagli e forse dare un chiaro esempio di come cambierebbe qualcosa se la bandiera fosse accesa o spenta?

Ho provato a cercare SO per domande simili ma non sono riuscito a trovare nulla che spiegasse il funzionamento della matematica veloce.

Risposte:


86

Come hai detto, consente ottimizzazioni che non preservano la rigorosa conformità IEEE.

Un esempio è questo:

x = x*x*x*x*x*x*x*x;

per

x *= x;
x *= x;
x *= x;

Poiché l'aritmetica in virgola mobile non è associativa, l'ordinamento e il factoring delle operazioni influenzeranno i risultati a causa dell'arrotondamento. Pertanto, questa ottimizzazione non viene eseguita in base a rigorosi comportamenti FP.

In realtà non ho verificato per vedere se GCC effettivamente esegue questa particolare ottimizzazione. Ma l'idea è la stessa.


25
@Andrey: per questo esempio, vai da 7 moltiplicati fino a 3.
Mistico

4
@Andrey: matematicamente, sarà corretto. Ma il risultato potrebbe differire leggermente negli ultimi bit a causa del diverso arrotondamento.
Mistico

1
Nella maggior parte dei casi, questa leggera differenza non ha importanza (relativamente all'ordine di 10 ^ -16 per double, ma varia a seconda dell'applicazione). Una cosa da notare è che le ottimizzazioni matematiche avanzate non aggiungono necessariamente "altro" arrotondamento. L'unico motivo per cui non è conforme IEEE è perché la risposta è diversa (anche se leggermente) da ciò che è scritto.
Mistico

1
@utente: l'entità dell'errore dipende dai dati di input. Dovrebbe essere piccolo rispetto al risultato. Ad esempio, se xè inferiore a 10, l'errore nell'esempio di Mystical sarà inferiore di circa 10 ^ -10. Ma se x = 10e20è probabile che l'errore sia di molti milioni.
Ben Voigt,

3
@stefanct in realtà su -fassociative-mathcui è inclusa nella -funsafe-math-optimizationsquale a sua volta è abilitato con -ffast-math Perché non GCC ottimizzare a*a*a*a*a*aper (a*a*a)*(a*a*a)?
phuclv,

256

-ffast-math fa molto di più di una semplice violazione della rigorosa conformità IEEE.

Prima di tutto, ovviamente, infrange la rigida conformità IEEE, consentendo ad esempio il riordino delle istruzioni su qualcosa che è matematicamente lo stesso (idealmente) ma non esattamente lo stesso in virgola mobile.

In secondo luogo, disabilita l' impostazione errnodopo le funzioni matematiche a istruzione singola, il che significa evitare una scrittura su una variabile thread-local (questo può fare una differenza del 100% per quelle funzioni su alcune architetture).

In terzo luogo, si assume che tutta la matematica sia finita , il che significa che non vengono effettuati controlli per NaN (o zero) laddove avrebbero effetti dannosi. Si presume semplicemente che ciò non accadrà.

In quarto luogo, consente approssimazioni reciproche per divisione e radice quadrata reciproca.

Inoltre, disabilita lo zero con segno (il codice presuppone che lo zero con segno non esista, anche se l'obiettivo lo supporta) e arrotondando la matematica, che consente tra l'altro una costante piegatura in fase di compilazione.

Infine, genera codice che presuppone che non possano verificarsi interruzioni hardware a causa della segnalazione / trapping della matematica (ovvero, se questi non possono essere disabilitati sull'architettura di destinazione e, di conseguenza , si verificano , non verranno gestiti).


15
Damon, grazie! Puoi aggiungere dei riferimenti? Come gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html " -ffast-math Imposta -fno-math-errno, -funsafe-math-ottimizzazioni, -ffinite-math-only, -fno-rounding-math, -fno-signaling -nans e -fcx-limited-range. Questa opzione determina la definizione della macro preprocessore FAST_MATH . "e qualcosa di glibc, come ( math.hnear math_errhandling)" Per impostazione predefinita, tutte le funzioni supportano sia la gestione errno che quella delle eccezioni. Nella modalità matematica veloce di gcc e se vengono definite funzioni inline questo potrebbe non essere vero. "
osgx

4
@javapowered: se è "pericoloso" dipende dalle garanzie di cui hai bisogno. -ffast-mathconsente al compilatore di tagliare alcuni angoli e rompere alcune promesse (come spiegato), che in generale non è pericoloso in quanto tale e non è un problema per la maggior parte delle persone. Per la maggior parte delle persone, è lo stesso, solo più veloce. Tuttavia, se il codice assume e si basa su queste promesse, il codice potrebbe comportarsi in modo diverso da quello previsto. Di solito, questo significa che il programma sembrerà funzionare bene, per lo più, ma alcuni risultati potrebbero essere "inattesi" (ad esempio, in una simulazione fisica, due oggetti potrebbero non collidere correttamente).
Damon,

2
@Royi: i due dovrebbero essere indipendenti l'uno dall'altro. -O2generalmente consente "ogni" ottimizzazione legale, ad eccezione di quelli che scambiano dimensioni per la velocità. -O3consente inoltre ottimizzazioni che scambiano le dimensioni per la velocità. Mantiene ancora la correttezza al 100%. -ffast-mathtenta di velocizzare le operazioni matematiche consentendo un comportamento "leggermente scorretto" che di solito non è dannoso, ma che sarebbe considerato errato dalla formulazione dello standard. Se il tuo codice è davvero molto diverso in termini di velocità su due compilatori (non solo 1-2%), verifica che il tuo codice sia rigorosamente conforme agli standard e ...
Damon

1
... produce zero avvisi. Inoltre, assicurati di non interferire con le regole di alias e cose come l'auto-vettorializzazione. In linea di principio, GCC dovrebbe funzionare almeno altrettanto bene (di solito meglio della mia esperienza) di MSVC. In caso contrario, probabilmente hai fatto un sottile errore che MSVC ignora ma che fa sì che GCC disabiliti un'ottimizzazione. Dovresti dare entrambe le opzioni se le vuoi entrambe, sì.
Damon,

1
@Royi: quel codice non mi sembra molto piccolo e semplice, non qualcosa che si potrebbe analizzare in profondità in pochi minuti (o addirittura ore). Tra le altre cose, comporta un aspetto apparentemente innocuo #pragma omp parallel for, e all'interno del corpo del loop stai leggendo e scrivendo entrambi gli indirizzi a cui fanno riferimento argomenti di funzioni, e fai una quantità non banale di ramificazioni. Come ipotesi non istruita, potresti bloccare le cache dall'invocazione dei thread definita dall'implementazione e MSVC potrebbe evitare erroneamente archivi intermedi che le regole di aliasing imporrebbero. Impossibile dirlo.
Damon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.