Live data from Hacker News

Optimizations Enabled by -ffast-Math

kristerw.github.io

71–80 of 117 posts

Re: Optimizations Enabled by -ffast-Math

#71
post #11

I found the following note for -ffinite-math-only and -fno-signed-zeros quite worrying: The program may behave in strange ways (such as not evaluating either the true or false part of an if-statement) if calculations produce Inf, NaN, or -0.0 when these flags are used. I always thought that -ffast-math was telling the compiler. "I do not care about floating point standards compliance, and I do not rely on it. So opti…

The whole rationale for the standard for floating-point operations is to specify the FP operations with such properties that a naive programmer will be able to write programs which will behave as expected. If you choose any option that is not compliant with the standard, that means, exactly as you have noticed, that you claim that you are an expert in FP computations and you know how to write FP programs that will gi…

> So yes, that means that you become responsible to either guarantee that erroneous results do not matter or that you will take care to always check the ranges of input operands, as "okl" has already posted, to ensure that no overflows, underflows or undefined operations will happen.

This is why I dislike the fact that it removes simple methods for checking if values are NaN, for example. I do not find it ergonomic to disable these checks.

Re: Optimizations Enabled by -ffast-Math

#72
post #11

I found the following note for -ffinite-math-only and -fno-signed-zeros quite worrying: The program may behave in strange ways (such as not evaluating either the true or false part of an if-statement) if calculations produce Inf, NaN, or -0.0 when these flags are used. I always thought that -ffast-math was telling the compiler. "I do not care about floating point standards compliance, and I do not rely on it. So opti…

I thought the idea was to never produce NaN or -0 in the first place, but it seems that's not the case.

Re: Optimizations Enabled by -ffast-Math

#73
post #23

If you use the LLVM D compiler you can opt in or out of these individually on a per-function basis. I don't trust them globally.

In Julia you can do `@fastmath ...`, which is basically just a find-and-replace of math operations and does not propagate into functions called in that code block, even when they are ultimately inlined. So what it does is: julia> @macroexpand @fastmath x + y :(Base.FastMath.add_fast(x, y)) And maybe that's a good thing, because the scope of @fastmath is as limited as it gets.

Yeah, I really like that Julia and LLVM allow applying it on a per-operation basis.

Because most of LLVM's backends don't allow for the same level of granularity, they do end up propagating some information more than I would like. For example, marking an operation as fast lets LLVM assume that it does not result in NaNs, letting nan checks get compiled away even though they themselves are not marked fast:

  julia> add_isnan(a,b) = isnan(@fastmath(a+b))
  add_isnan (generic function with 1 method)
  
  julia> @code_llvm debuginfo=:none add_isnan(1.2,2.3)
  define i8 @julia_add_isnan_597(double %0, double %1) #0 {
  top:
    ret i8 0
  }
meaning it remains more dangerous to use than it IMO should be. For this reason, LoopVectorization.jl does not apply "nonans".

Re: Optimizations Enabled by -ffast-Math

#74

Earlier quoted context omitted.

I think the documentation is fairly clear: "Allow optimizations for floating-point arithmetic that assume that arguments and results are not NaNs or +-Infs." The obvious implication is that is that arguments and results are always finite and as a programmer you are responsible of guaranteeing the correct preconditions. Certainly do not use finite-math-only when dealing with external data.

>Certainly do not use finite-math-only when dealing with external data. so... never?

let me rephrase: data which you cannot guarantee meet the preconditions. Either the input is known good data or you sanitize it through some code path not compiled with fast-math.

Re: Optimizations Enabled by -ffast-Math

#75
post #61

Earlier quoted context omitted.

This depends how you define “gone wrong”. In evaluating rational functions (a useful tool for approximating all sorts of other functions), one efficient and well behaved algorithm is the “barycentric formula”, based on interpolating function values at various specific input points. When the input is one of those points directly, this formula results in Inf / Inf = NaN. Evaluation code needs to check for this case, an…

> When the input is one of those points directly, That's against one of the rules of well-behaved floating point programming: Never test for equality.

It's worth being a bit more explicit about this.

The appearance of NaNs doesn't result from breaking the "never test for equality" rule. It happens when the point where you're evaluating the function just happens to exactly match one of the interpolation points.

But if you decide to deal with the NaN issue by (1) testing whether your input exactly matches any of the interpolation points, or (2) testing the result for NaN, then you're testing for equality or doing something very similar, and then you may have one of the problems the "never test for equality" is meant to prevent: if bad stuff happens when x exactly equals one of the x_j, then perhaps less-obvious bad stuff happens when x almost exactly equals one of the x_j, and an equality test won't catch that.

So, does it? Actually, I think not. Suppose x = xj+h with h very small. Then your interpolant is (h times something of normal size) times (1/h times something of normal size + smaller terms) and although one of those is very small and the other very large, ordinary floating-point arithmetic doesn't care about that. There's no catastrophic cancellation or anything. And floating point has much more dynamic range than precision, so to speak, so until h literally reaches zero you aren't running into trouble. (I guess there might be ways of organizing the calculation that do give you catastrophic cancellation, but if you do it the obvious way then that doesn't happen.)

So in this particular case you really could just test for equality or test for NaNs. I think.

(For some interesting investigation of other aspects of the numerical stability of this kind of interpolation, see https://people.maths.ox.ac.uk/trefethen/publication/PDF/2011.... But note that the authors of this paper are using the formula for extrapolation or, as they prefer to call it, "numerical analytic continuation".)

Re: Optimizations Enabled by -ffast-Math

#76

Earlier quoted context omitted.

Flushing denormals to zero only matters if your calculations are already running into the lower end of floating-point exponents (and even with denormals, if they're doing that, they're going to run into lost precision anyway sooner or later). The useful thing denormals do is make the loss of precision at that point gradual, instead of sudden. But you're still losing precision, and a few orders of magnitude later you'…

Your arguments are correct, but the conclusion does not result from them. If we assume that underflows happen in your program and this, as you say, is a sign that greater problems will be caused by that, then you must not enable flush-to-zero, but you must enable trap-on-underflow, to see where underflows happen and to investigate the reason and maybe rearrange your formulas to avoid the too small results. Flush-to-z…

> Opinions obviously vary, but I have never seen any good use case for flush-to-zero.

The classical use case is real-time audio, where an IIR filter may have quite slow decay, such that the signal stays in the subnormal regime for many samples. If this happens and your hardware has significant stalls for subnormal data, you may miss your real-time deadline resulting in clicking or other audio corruption.

Re: Optimizations Enabled by -ffast-Math

#77
post #2

The question is how to enable all this in Rust... Right now there's no simple way to just tell Rust to be fast & loose with floats.

There are fast float intrinsics: https://doc.rust-lang.org/std/intrinsics/fn.fadd_fast.html

but better support dies in endless bikeshed of:

• People imagine enabling fast float by "scope", but there's no coherent way to specify that when it can mix with closures (even across crates) and math operators expand to std functions defined in a different scope.

• Type-based float config could work, but any proposal of just "fastfloat32" grows into a HomerMobile of "Float"

• Rust doesn't want to allow UB in safe code, and LLVM will cry UB if it sees Inf or Nan, but nobody wants compiler inserting div != 0 checks.

• You could wrap existing fast intrinsics in a newtype, except newtypes don't support literals, and

Re: Optimizations Enabled by -ffast-Math

#78

Earlier quoted context omitted.

> Generally if you're seeing a NaN/Inf something has gone wrong That’s a bold claim.

Can you think of a function where the input is valid, the output is NaN and nothing has gone wrong in the process? I can't think of any, haven't experienced any, not heard of any examples of it, so you're welcome to break my ignorance on the subject.

JavaScript's parseInt and parseFloat return NaN on something as benign as "well, that wasn't a number".

    var jsonish = "...";
    var number = parseFloat(jsonish);
    if (!isNaN(number)) {
        // deserialized a number
    } else {
        // ...try deserializing as a boolean or string next...
    }
Fast math optimizations can break code like this by breaking isNaN.

I was porting a C++ project to a certain platform - and that platform enabled a -ffast-math equivalent by default in Release (but not Debug) builds! This broke duktape, a JS engine said project embedded, in some nasty and subtle ways. Instead of storing a number/pointer/??? (8 bytes) + type tag (4? bytes) for each dynamically typed JS value, duktape can bit-pack values into a single 8 byte "double" value by storing object/string handles as NaN values - this isn't an uncommon trick for dynamically typed scripting stuff:

https://github.com/svaarala/duktape/blob/c3722054ea4a4e50f48...

Naturally, the -ffast-math equivalent broke isNaN checks, which caused random object/string handles to be mistakenly reinterpreted as "numbers" - but only in Release builds, for this one particular platform, in one rarely taken branch, so neither QA nor CI caught it, leading to hours of manufacturing a repro case, stepping through an absurd amount of code, and then finally looking at the default build rules and facepalming.

Cursing the platform vendor under my breath, I overrode the defaults to align with the defaults of every other config x platform combination we already had: no fast math. If you want those optimizations, use SSE-friendly NaN-avoiding intrinsics - or, if you must use the compiler flags, ensure you do so consistently across build configs and platforms, perhaps limited to a few TUs or modules if possible. This allows you to have a chance at using your Debug builds to debug the resulting "optimizations".

Re: Optimizations Enabled by -ffast-Math

#79

Earlier quoted context omitted.

Generally if you're seeing a NaN/Inf something has gone wrong, It's very difficult to gracefully recover from and if you tried I think you would lose both sanity and performance! Regarding performance, the cost of a real division is about 3-4 orders worse performance than an if statement that is very consistent, but the usual way is to have fast/safe versions of functions, where you need performance and can deduce if…

> Generally if you're seeing a NaN/Inf something has gone wrong That’s a bold claim.

As a scientist, it depends on what you mean by wrong. It's nice to push an array of numbers through some array operation. If some of the outputs look like NaN or Inf then that tells me something went wrong in the operation and to take a closer look. If some optimizer was told that NaN or Inf couldn't happen, meaning that they wouldn't be generated, then some of this output could be pure nonsense and I wouldn't notice. As NaNs propagate through the calculation they're extremely helpful to indicate something went wrong somewhere in the call chain.

NaN is also very useful itself as a data missing value in an array. If you have some regularly sampled data, with some items missing, it makes a lot of things easier to populate the holes with values which guarantee they won't produce misleading output.

Re: Optimizations Enabled by -ffast-Math

#80
Fascinating to learn that something labelled 'unsafe' stands a reasonable chance of making a superior mathematical/arithmetic choice (even if it doesn't match on exactly to the unoptimized FP result).

If you're taking the ratio of sine and cosine, I'll bet that most of the time you're better off with tangent....

Post reply on HN