Live data from Hacker News

Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

1f6042.blogspot.com

71–80 of 152 posts

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#71
post #59
post #47

Earlier quoted context omitted.

You can't fully disable fast-math per-library, moreover a library compiled with fast-math might also introduce inaccuracies in a seemingly unrelated library or application code in the same executable. The reason is that fast-math enables some dynamic initialization of the library that changes the floating point environment in some ways.

> You can’t fully disable fast-math per library Can you elaborate? What fast-math can sneak into a library that disabled fast-math at compile time? > fast-math enables some dynamic initialization of the library that changes the floating point environment in some ways. I wasn’t aware of this, I would love to see some documentation discussing exactly what happens, can you send a link?

https://github.com/llvm/llvm-project/issues/57589

Turn on fast-math, it flips the FTZ/DAZ bit for the entire application. Even if you turned it on for just a shared library!

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#72
post #48
post #46

Earlier quoted context omitted.

Another PSA is that dynamic libraries compiled with fast-math will also introduce inaccuracies in unrelated libraries in the same executable, as they introduce dynamic initialization that globally changes the floating point environment.

This would only affect code that uses the old-school x87 floating point instructions, though? The x87 FPU unit indeed has scary global state that can make your doubles behave like floats in secret and silence. I would think practically all modern FPU code on x86-64 would be using the SIMD registers which have explicit widths.

> This would only affect code that uses the old-school x87 floating point instructions, though?

Actually, no, the x87 FPU instructions are the only ones that won't be affected.

It sets the FTZ/DAZ bits, which exist for SSE instructions but not x87 instructions.

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#73
post #11

On gcc 13, the difference in assembly between the min(max()) version and std::clamp is eliminated when I add the -ffast-math flag. I suspect that the two implementations handle one of the arguments being NaN a bit differently. https://gcc.godbolt.org/z/fGaP6roe9 I see the same behavior on clang 17 as well https://gcc.godbolt.org/z/6jvnoxWhb

You (celegans25) probably know this but here is a PSA that -ffast-math is really -finaccurate-math. The knowledgeable developer will know when to use it (almost never) while the naive user will have bugs.

One of the things that you can do with D and as far as I know Julia is enable specific optimizations locally e.g. allow FMAs here and there, not globally.

fast-math is one of the dumbest things we have as an industry IMO.

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#74
post #48
post #46

Earlier quoted context omitted.

Another PSA is that dynamic libraries compiled with fast-math will also introduce inaccuracies in unrelated libraries in the same executable, as they introduce dynamic initialization that globally changes the floating point environment.

This would only affect code that uses the old-school x87 floating point instructions, though? The x87 FPU unit indeed has scary global state that can make your doubles behave like floats in secret and silence. I would think practically all modern FPU code on x86-64 would be using the SIMD registers which have explicit widths.

You're mistaking something else for the rounding mode and subnormal handling flags.

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#75
post #62

Earlier quoted context omitted.

> Fast math is only slightly less accurate 'slightly'? Last I checked, -Ofast completely breaks std::isnan and std::isinf--they always return false.

Hopefully it was clear from the rest of my comment that I was talking about in-range floats there. I wouldn’t necessarily call inf & nan handling an accuracy issue, that’s more about exceptional cases, but to your point I would have to agree that losing std::isinf is kinda bad since divide by zero is probably near the very top of the list of things most people using floats casually might have to deal with. Which comp…

My experience is with gcc and clang on x86. I generally agree with you regarding accuracy, which is why I was quite surprised when I first discovered that -Ofast breaks isnan/isinf.

Even if I don't care about the accuracy differences, I still need a way to check for invalid input data. The upshot is that I had to roll my own isnan and isinf to be able to use -Ofast (because it's actually the underlying __builtin_xxx intrinsics that are broken), which still seems wrong to me.

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#76
post #37

Earlier quoted context omitted.

Why do you say almost never? Don’t let the name scare you; all floating point math is inaccurate. Fast math is only slightly less accurate, I think typically it’s a 1 or maybe 2 LSB difference. At least in CUDA it is, and I think many (most?) people & situations can tolerate 22 bits of mantissa compared to 23, and many (most?) people/situations aren’t paying attention to inf/nan/exception issues at all. I deal with a…

Here are some of the problems with fast-math: * It links in an object file that enables denormal flushing globally, so that it affects all libraries linked into your application, even if said library explicitly doesn't want fast-math. This is seriously one of the most user-hostile things a compiler can do. * The results of your program will vary depending on the exact make of your compiler and other random attributes…

Thank you, great points. I’d have to agree that disabling denorms globally is pretty bad, even if (or maybe especially if) caring about denorms is rare.

> Fast-math can cause hard range guarantees to fail. Maybe you’ve got code that you can prove that, even with rounding error, the result will still be >= 0.

Floats do this too, it’s pretty routine to bump into epsilon out-of-range issues without fast-math. Most people don’t prove things about their rounding error, and if they do, it’s easy for them to account for 3 ULPs of fast-math error compared to 1/2 ULP for the more accurate operations. Like, nobody who knows what they’re doing will call sqrt() on a number that is fresh out of a multiplier and might be anywhere near zero without testing for zero explicitly, right? I’m sure someone has done it, but I’ve never seen it, and it ranks high on the list of bad ideas even if you steer completely clear of fast-math, no?

I guess I just wanted to resist the unspecific parts of the FUD just a little bit. I like your list a lot because it’s specific. Fast-math does carry some additional risks for accuracy sensitive code, and clearly as you and others showed, can infect and impact your whole app, and it can sometimes lead to situations where things break that wouldn’t have happened otherwise. But I think in the grand scheme these situations are quite rare compared to how often people mess up regular floating point math. For a very wide swath of people doing casual arithmetic, fast-math is not likely to cause more problems than floats cause, but it’s fair to want to be careful and pay attention.

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#77
I see that the assembly instructions are different, but what's the performance difference? Personally, I don't care about the number of instructions used, as long as it's faster. With things like store forwarding and register files, a lot of those movs might be treated as noops.

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#78
post #38

Earlier quoted context omitted.

That was what Reagan said about the Soviet Union, not what was said in the Soviet Union. Correct me if I'm wrong.

https://en.wikipedia.org/wiki/Trust,_but_verify > Trust, but verify (Russian: доверяй, но проверяй, tr. doveryay, no proveryay, IPA: [dəvʲɪˈrʲæj no prəvʲɪˈrʲæj]) is a Russian proverb, which is rhyming in Russian. The phrase became internationally known in English after Suzanne Massie, a scholar of Russian history, taught it to Ronald Reagan, then president of the United States, the latter of whom used it on several o…

Thanks for the clarification/explanation!

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#79
post #6

I'm a heavy std::clamp user, but I'm considering replacing it with min+max because of the uncertainty about what will happen when lo > hi. On windows it triggers an assertion, while other platforms just do a min+max in one or the other order. Of course, this should never happen but can be difficult to guarantee when the limits are derived from user inputs.

Will min+max help you? What do you expect the answer to be when lo > hi? What certainty should std::clamp have? Using min+max on a number that’s between lo+hi when lo>hi will always return either lo or hi, and never your input value.

Re: Std: Clamp generates less efficient assembly than std:min(max,std:max(min,v))

#80
post #54

Depending on the order of the arguments to min max you'll get an extra move instruction [1]: std::min(max, std::max(min, v)); maxsd xmm0, xmm1 minsd xmm0, xmm2 std::min(std::max(v, min), max); maxsd xmm1, xmm0 minsd xmm2, xmm1 movapd xmm0, xmm2 For min/max on x86 if any operand is NaN the instruction copies the second operand into the first. So the compiler can't reorder the second case to look like the first (to lea…

I think the libstdc++ implementation does indeed have the comparisons ordered in the way that you describe. I stepped into the std::clamp() call in gdb and got this:

    ┌─/usr/include/c++/12/bits/stl_algo.h──────────────────────────────────────────────────────────────────────────────────────
    │     3617     \*  @pre `_Tp` is LessThanComparable and `(__hi 
    │     3620      constexpr const _Tp&
    │     3621      clamp(const _Tp& __val, const _Tp& __lo, const _Tp& __hi)
    │     3622      {
    │     3623        __glibcxx_assert(!(__hi   3624        return std::min(std::max(__val, __lo), __hi);
    │     3625      }
    │     3626
Post reply on HN