Understanding FastMath
While adding support for Xcode 13 to our iOS PDF SDK, we stumbled upon an interesting issue in our PDF renderer. Everything worked fine in debug builds, but when using the new developer tools to compile in release configuration, some PDF elements were missing. After a lot of printf
debugging, it became apparent that a floatingpoint NaN
check started failing, which eventually led us to the ffastmath
optimization we introduced a few years ago. Luckily, we managed to catch this issue during internal QA before it became a problem for our customers. As we clearly underestimated the risks associated with this optimization, it seemed prudent to take a closer look at it. It turns out that, like with almost anything else relating to IEEE floatingpoint math, it’s a rabbit hole full of surprising behaviors.
What Is FastMath?
ffastmath
is a compiler flag that enables a set of aggressive floatingpoint optimizations. The flag is shorthand for a collection of different optimization techniques, each having its own compiler flag. The exact set of optimizations differs between compilers, but it generally includes a set of optimizations that leverage algebraic rules that hold for real numbers, but not necessarily for IEEE floats.
Enabling ffastmath
will break strict IEEE compliance for your application and could result in changes in behavior. At best, it might affect the precision of computed numbers. At worst, it might significantly affect the program’s branching and produce completely unexpected results.
In Xcode, the fastmath optimization can be enabled with the GCC_FAST_MATH
build setting. You can find it listed as Relax IEEE Compliance in the Xcode Build Settings UI under Apple Clang  Code Generation.
Clang
To better understand the optimizations ffastmath
enables, we can look at the specific set of behaviors (compiler flags) the option implies when using the Clang compiler:

ffinitemathonly
— Shorthand forfnohonorinfinities
andfnohonornans
.
fnohonorinfinities
— The compiler assumes there are no infinities (+/Inf
) in the program (neither in the arguments nor in the results of floatingpoint arithmetic). 
fnohonornans
— The compiler assumes there are noNaN
s in the program (neither in the arguments nor in the results of floatingpoint arithmetic).


fnomatherrno
— Enables optimizations that might cause standard C math functions to not seterrno
. This avoids a write to a threadlocal variable and enables inlining of certain math functions. 
funsafemathoptimizations
— Shorthand for a set of unsafe floatingpoint optimizations.
fassociativemath
— Enables optimizations leveraging the associative property of real numbers, i.e. (x + y) + z => x + (y + z). Due to rounding errors, this algebraic law typically doesn’t apply to IEEE floatingpoint numbers. 
freciprocalmath
— Allows division operations to be transformed into multiplication by a reciprocal, e.g. x = a / c; y = b / c; => tmp = 1.0 / c; x = a _ tmp; y = b _ tmp. This can be significantly faster than division, but it can also be less precise. 
fnosignedzeros
— Enables optimizations that ignore the sign of floatingpoint zeros. Without this option, IEEE arithmetic predicts specific behaviors for +0.0 and 0.0 values, which prohibit the simplification of expressions like x+0.0 or 0.0*x. 
fnotrappingmath
— The compiler assumes floatingpoint exceptions won’t ever actually invoke a signal handler, which enables speculative execution of floatingpoint expressions and simple optimizations like this one.


ffpcontract=fast
— Enables the use of floatingpoint contraction instructions, such as fused multiplyadd (FMA). In turn, the floatingpoint contraction instructions combine two separate floatingpoint operations into a single operation. Those instructions can affect floatingpoint precision, because instead of rounding after each operation, the processor may round only once after both operations.
With Clang (and GCC), you can assume that ffastmath
will also be used when specifying the Ofast
optimization level.
Dealing with Finite Math
One of the more controversial optimizations from the list above is ffinitemathonly
, together with its two suboptions, fnohonorinfinities
and fnohonornans
. The official Clang documentation doesn’t go into too much detail and just defines ffinitemathonly
as allowing “floatingpoint optimizations that assume arguments and results are not NaN
s or +Inf.”
This option enables a set of optimizations for arithmetic expressions, which seem intuitive for real numbers but aren’t generally possible when we have to deal with NaN
and Inf
values in floatingpoint numbers. The option fits well with fnosignedzeros
to enable an ever greater set of optimizations. So far, so good.
The controversy starts when we take a look at the behavior of a function like isnan
. How should this check behave when we’re using ffinitemathonly
? Should it make a real check, or should the compiler just optimize it to false
? With the current definition of this option, we’re essentially telling the compiler there will never be any NaN
s in the program, so it’s technically free to optimize the check to a constant false
.
While this optimization might make sense intuitively, provided you first carefully read the compiler documentation for ffastmath
and its suboptions, it also causes quite a few problems. For one, it breaks some reasonable workflows where we’d want to validate input data or where NaN
s would be used as memoryefficient sentinels to mark invalid data in floatingpoint data structures. This is precisely the trap we fell into. Some of our C++ code uses NaN
s to indicate invalid values for PDF primitives. Those values are checked with isnan
, and branching is done accordingly. The code remained working fine for years after we first introduced the ffastmath
option. But it was always undefined behavior, and all it took was a compiler update to turn it into a regression.
The ffinitemathonly
optimization also causes inconsistencies where isnan
checks will behave differently if they’re provided by the compiler or a library with different optimization settings (e.g. libc
). There are also other standard APIs that might produce surprising behaviors — e.g. std::numeric_limits<T>::has_quiet_NaN()
might still claim that NaN
s are supported even when the optimization is applied. You could also go so far as to say that double
and double
under ffinitemathonly
should be considered different types due to the differences in behavior you’d see if your project uses ffinitemathonly
selectively.
Another way to look at the logic of having NaN
checks optimized out is from a pure performance point of view. It should be fairly safe to assume that code that extensively uses isnan
, and could therefore benefit the most by having NaN
checks removed, is also the code that most likely cares about the correct output of those checks — and therefore can’t use ffinitemathonly
.
The ffinitemathonly
option could be made safer if we changed the definition of ffinitemathonly
to only apply to arithmetic expressions, but otherwise still allow NaN
values. In other words, the assumption of no NaN
s would be applied to mathematical expressions and functions, but not to tests like isnan
. This alternative has been proposed a few times already — most recently in this fairly lengthy llvmdev mailing list thread. In it, you can see that there are certainly good arguments to be made for either behavior, and at least for now, it appears as though the discussion ended in a stalemate.
Performance Impact
We could have refactored our code to not use NaN
s in this way, or employed a number of workarounds to fix the issue with our isnan
checks, like using integer operations to check the bits corresponding to NaN
, or selectively disabling ffinitemathonly
in certain files. However, we didn’t do any of that. Instead, we opted to play it safe, and we globally disabled ffastmath
.
The option was introduced before we had reliable performance tests, so I was curious to see what impact this would have. To my surprise, there were no measurable differences outside of the standard deviation we already see when repeating tests multiple times. This isn’t to say that certain floatingpoint operations didn’t in fact become slower. They most likely did. However, in our case, they don’t seem to be causing any actual bottlenecks.
Conclusion
As you can see, ffastmath
is unfortunately not just a harmless optimization that makes your app run faster; it can also effect the correctness of your code. And even if it doesn’t do that right now, it might do that in the future with new compiler revisions.
Unless you see actual performance bottlenecks with your floatingpoint calculations, it’s best to avoid ffastmath
. And there’s a good chance it won’t have a significant impact on the performance characteristics of your average program. It didn’t make much of a difference for our renderer, even though it has to deal with a lot of floatingpoint operations.
If your performance tests do indicate that ffastmath
makes a difference, then be sure to spend some time auditing your floatingpoint calculations and control flow to avoid the more obvious pitfalls, such as the use of isnan
and isinf
. In the end, most other issues will still be very hard to notice, so you’ll have to accept a certain amount of risk. For us, the decision was easy — it’s just not worth the trouble.
Matej is a software engineering leader from Slovenia. He began his career freelancing and contributing to open source software. Later, he joined PSPDFKit, where he played a key role in creating its initial products and teams, eventually taking over as the company’s Chief Technology Officer. Outside of work, Matej enjoys playing tennis, skiing, and traveling.