Co właściwie robi ffast-math gcc?

144

Rozumiem, że --ffast-mathflaga gcc może znacznie zwiększyć prędkość operacji typu float i wykracza poza standardy IEEE, ale nie mogę znaleźć informacji o tym, co naprawdę się dzieje, gdy jest włączony. Czy ktoś może wyjaśnić niektóre szczegóły i może podać jasny przykład, jak coś by się zmieniło, gdyby flaga była włączona lub wyłączona?

Próbowałem przekopać się przez SO w poszukiwaniu podobnych pytań, ale nie mogłem znaleźć nic wyjaśniającego działanie ffast-matath.

Ponml
źródło

Odpowiedzi:

86

Jak wspomniałeś, umożliwia optymalizacje, które nie zachowują ścisłej zgodności z IEEE.

Oto przykład:

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

do

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

Ponieważ arytmetyka zmiennoprzecinkowa nie jest asocjacyjna, porządkowanie i rozkładanie operacji na czynniki wpłynie na wyniki z powodu zaokrąglenia. Dlatego ta optymalizacja nie jest wykonywana przy ścisłym zachowaniu FP.

Właściwie nie sprawdziłem, czy GCC faktycznie wykonuje tę konkretną optymalizację. Ale idea jest taka sama.

Mistyczne
źródło
25
@Andrey: W tym przykładzie przechodzisz od mnożenia 7 do 3.
Mysticial
4
@Andrey: Matematycznie będzie poprawna. Jednak wynik może się nieznacznie różnić w kilku ostatnich bitach ze względu na różne zaokrąglenia.
Mysticial
1
W większości przypadków ta niewielka różnica nie ma znaczenia (względnie rzędu 10 ^ -16 dla double, ale różni się w zależności od aplikacji). Należy zauważyć, że optymalizacje ffast-matematyczne niekoniecznie dodają „więcej” zaokrągleń. Jedynym powodem, dla którego nie jest zgodny ze standardem IEEE, jest to, że odpowiedź jest inna (choć nieznacznie) od tego, co jest napisane.
Mysticial
1
@user: wielkość błędu zależy od danych wejściowych. Powinien być mały w stosunku do wyniku. Na przykład, jeśli xjest mniejsze niż 10, błąd w przykładzie Mystical spadnie o około 10 ^ -10. Ale jeśli x = 10e20błąd prawdopodobnie wyniesie wiele milionów.
Ben Voigt,
3
@stefanct to rzeczywiście temat -fassociative-math, który jest zawarty w -funsafe-math-optimizationsktórym z kolei jest włączona -ffast-math Dlaczego nie GCC zoptymalizować a*a*a*a*a*ado (a*a*a)*(a*a*a)?
phuclv
255

-ffast-math robi o wiele więcej niż tylko łamanie ścisłej zgodności z IEEE.

Przede wszystkim oczywiście się psuje ścisłą zgodność z IEEE, pozwalając np. Na zmianę kolejności instrukcji na coś, co jest matematycznie takie samo (idealnie), ale nie dokładnie takie samo w postaci zmiennoprzecinkowej.

Po drugie, wyłącza ustawianie errnopo funkcjach matematycznych składających się z jednej instrukcji, co oznacza unikanie zapisu do zmiennej lokalnej wątku (może to spowodować 100% różnicę dla tych funkcji na niektórych architekturach).

Po trzecie, przyjmuje założenie, że cała matematyka jest skończona , co oznacza, że ​​nie są przeprowadzane żadne kontrole dla NaN (lub zera), w którym miałyby one szkodliwe skutki. Po prostu zakłada się, że tak się nie stanie.

Po czwarte, umożliwia odwrotne przybliżenie dzielenia i odwrotności pierwiastka kwadratowego.

Ponadto wyłącza zero ze znakiem (kod zakłada, że ​​zero ze znakiem nie istnieje, nawet jeśli cel je obsługuje) i zaokrąglanie matematyki, co umożliwia między innymi stałe zawijanie w czasie kompilacji.

Na koniec generuje kod, który zakłada, że ​​żadne przerwania sprzętowe nie mogą się zdarzyć z powodu matematyki sygnalizującej / pułapkowej (to znaczy, jeśli nie można ich wyłączyć w architekturze docelowej i w konsekwencji się zdarzają , nie będą obsługiwane).

Damon
źródło
15
Damon, dzięki! Czy możesz dodać referencje? Na przykład gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html " -ffast-math Ustawia -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling -nans i -fcx-limited-range. Ta opcja powoduje zdefiniowanie makra preprocesora FAST_MATH . "i coś z glibc, np. ( math.hblisko math_errhandling)" Domyślnie wszystkie funkcje obsługują zarówno obsługę błędów, jak i wyjątków. W szybkim trybie matematycznym gcc i jeśli zdefiniowano funkcje wbudowane, może to nie być prawda. "
osgx
4
@javapowered: To, czy jest to „niebezpieczne”, zależy od potrzebnych gwarancji. -ffast-mathpozwala kompilatorowi iść na skróty i złamać pewne obietnice (jak wyjaśniono), co generalnie nie jest niebezpieczne jako takie i nie stanowi problemu dla większości ludzi. Dla większości ludzi jest tak samo, tylko szybciej. Jeśli jednak Twój kod zakłada i opiera się na tych obietnicach, może on zachowywać się inaczej niż się spodziewasz. Zwykle oznacza to, że program wydaje się działać dobrze, ale niektóre wyniki mogą być „nieoczekiwane” (np. W symulacji fizyki dwa obiekty mogą nie zderzać się prawidłowo).
Damon
2
@Royi: Obaj powinni być od siebie niezależni. -O2ogólnie umożliwia „każdą” legalną optymalizację, z wyjątkiem tych, które handlują wielkością dla szybkości. -O3umożliwia również optymalizacje, które pozwalają na zmianę rozmiaru dla szybkości. Nadal zachowuje 100% poprawność. -ffast-mathusiłuje przyspieszyć operacje matematyczne, dopuszczając „nieco niepoprawne” zachowanie, które zwykle nie jest szkodliwe, ale byłoby uznane za nieprawidłowe w świetle normy. Jeśli kod jest rzeczywiście znacznie różnią się prędkością na dwóch kompilatorów (nie tylko 1-2%), a następnie sprawdzić, czy kod jest zgodny ściśle norm i ...
Damon
1
... generuje zero ostrzeżeń. Upewnij się również, że nie przeszkadza Ci w tworzeniu aliasów reguł i takich rzeczy, jak automatyczna wektoryzacja. Zasadniczo GCC powinien działać co najmniej tak dobrze (zwykle lepiej z mojego doświadczenia) jak MSVC. Jeśli tak nie jest, prawdopodobnie popełniłeś subtelny błąd, który MSVC po prostu ignoruje, ale który powoduje, że GCC wyłącza optymalizację. Powinieneś dać obie opcje, jeśli chcesz mieć obie, tak.
Damon
1
@Royi: Ten kod nie wygląda dla mnie na naprawdę mały i prosty, nie jest to coś, co można by dogłębnie przeanalizować w kilka minut (a nawet godzin). Między innymi obejmuje pozornie nieszkodliwe #pragma omp parallel for, a wewnątrz treści pętli zarówno czytasz, jak i piszesz do adresów wskazanych przez argumenty funkcji, a także wykonujesz nietrywialne rozgałęzienia. Jako niewykształcone przypuszczenie, możesz rzucać pamięć podręczną z wewnątrz zdefiniowanego przez implementację wywołania wątków, a MSVC może niepoprawnie unikać magazynów pośrednich, których wymagają reguły aliasingu. Trudno powiedzieć.
Damon