Live data from Hacker News

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

1f6042.blogspot.com

141–150 of 152 posts

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

#141
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…

Yes, I arrived at the same conclusion.

The various code snippets in the article don't compute the same "function". The order between the min() and max() matters even when done "by hand". This is apparent when min is greater than max as the results differ in the choice of the boundaries.

Funny that for such simple functions the discussion can become quickly so difficult/interesting.

Some toying around with the various implementations in C [1]:

[1]: https://godbolt.org/z/d4Tcdojx3

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

#142
post #73
post #11

Earlier quoted context omitted.

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.

Totally agreed. In Julia we use https://github.com/SciML/MuladdMacro.jl all over the place so that way it's contextual and does not bleed into other functions. fast-math changing everything is just... dangerous.

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

#143
post #59

Earlier quoted context omitted.

> 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!

That's only one small part of -ffast-math/-Ofast though and not a very scary one at that.

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

#144
post #92
post #66

Earlier quoted context omitted.

That’s fair. Ideally transitive dependencies should be completely hidden from you. Hopefully the author of the library you include directly has heeded the instructions of libraries they depend on. Hey I grant and acknowledge that using fast-math carries a little risk of surprises, we don’t necessarily need to try to think of corner cases. I’m mostly pushing back a little because using floats at all carries almost as…

> A lot of people seem to use floats without knowing how inaccurate floats are, Small nit, but floats aren't inaccurate, they have non uniform precision. Some float operations can be inaccurate, but that's rather path dependent... One problem with -ffast-math is that a) it sounds appealing and b) people don't understand floats, so lots of people turn it on without understanding what it does, and that can introduce su…

> Sometimes in computational code it makes sense e.g. to get rid of denorms, but a very small fraction of programmers understand this properly, or ever will.

"Some times" here being almost all the time. It is rare that your code will break without denormals if it doesn't already have precision problems with them.

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

#145

Earlier quoted context omitted.

Because compilers can and have implemented sqrt in terms of rsqrt which is .. fun to work with. This also on SSE.

I spent most of my career working with rsqrt haha. And had my fair share of non-754 architectures too! Every 754 architecture (including SSE) I've worked on has an accurate sqrt(). I'm assuming you're talking about with "fast math" enabled? In which case all bets are off anyway!

No; compilers have done this even without fast-math. Gcc does not seem to do this anymore, but still does plenty of unsafe optimizations by default, like FMA.

Or maybe the library you use...

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

#146

Earlier quoted context omitted.

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!

That's only one small part of -ffast-math/-Ofast though and not a very scary one at that.

But it's an example of -ffast-math affecting separately compiled libraries.

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

#147
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…

Yes, I arrived at the same conclusion. The various code snippets in the article don't compute the same "function". The order between the min() and max() matters even when done "by hand". This is apparent when min is greater than max as the results differ in the choice of the boundaries. Funny that for such simple functions the discussion can become quickly so difficult/interesting. Some toying around with the various…

Yes, you are correct, the faster clamp is incorrect because it does not return v when v is equal to lo and hi.

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

#148
post #3

Both recent GCC and Clang are able to generate the most optimal version for std::clamp() if you add something like -march=znver1, even at -O1 [0]. Interesting! [0] https://godbolt.org/z/YsMMo7Kjz

Even with -march=znver1 at -O3 the compiler still generates fewer lines of assembly for the incorrect clamp compared to the correct clamp for this "realistic" code:

https://godbolt.org/z/WMKbeq5TY

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

#149
post #4

Clang generates the shortest of these if you target sandybridge, or x86-64-v3, or later. The real article that's buried in this article is that compilers target k8-generic unless you tell them otherwise, and the features and cost model of opteron are obsolete. Always specify your target.

Even with -march=x86-64-v4 at -O3 the compiler still generates fewer lines of assembly for the incorrect clamp compared to the correct clamp for this "realistic" code:

https://godbolt.org/z/hd44KjMMn

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

#150
post #85
post #80

Earlier quoted context omitted.

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,…

Thanks for sharing. I don't know if the C++ standard mandates one behavior or another, it really depends on how you want clamp to behave if the value is NaN. std::clamp returns NaN, while the reverse order returns the min value.

Based on my reading of cppreference, it is required to return negative zero when you do std::clamp(-0.0f, +0.0f, +0.0f) because when v compares equal to lo and hi the function is required to return v, which the official std::clamp does but my incorrect clamp doesn't.
Post reply on HN