Live data from Hacker News

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

1f6042.blogspot.com

11–20 of 152 posts

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

#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.

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

#12
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.

I hope they fix it. Thats quite a basic functional unit for it to be a footgun all on its own.

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

#14
post #2

If you benchmark these, you'll likely find the version with the jump edges out the one with the conditional instruction in practice.

Compilers often under-generate conditional instructions. They implicitly assume (correctly) that most branches you write are 90/10 (ie very predictable), not 50/50. The branches that actually are 50/50 suffer from being treated as being 90/10.

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

#15
post #2

If you benchmark these, you'll likely find the version with the jump edges out the one with the conditional instruction in practice.

FYI. https://quick-bench.com/q/sK9t9GoFDRkx9XxloUUbB8Q3ht4'

Using this microbenchmark on an Intel Sapphire Rapids CPU, compiled with march=k8 to get the older form, takes ~980ns, while compiling with march=native gives ~570ns. It's not at all clear that the imperfection the article describes is really relevant in context, because the compiler transforms this function into something quite different.

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

#16
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.

Ehh, not so much inaccurate, more of a "floating point numbers are tricky, let's act like they aren't".

Compilers are pretty skittish about changing the order of floating point operations (for good reason) and ffast-math is the thing that lets them transform equations to try and generate faster code.

IE, instead of doing "n / 10" doing "n * 0.1". The issue, of course, being that things like 0.1 can't be perfectly represented with floats but 100 / 10 can be. So now you've introduced a tiny bit of error where it might not have existed.

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

#17
I did a double take on this because I wrote a blog post about this topic a few months ago and came to a very different conclusion, that the results are effectively identical on clang and gcc is just weird.

Then I realized that I was writing about compiling for ARM and this post is about x86. Which is extra weird! Why is the compiler better tuned for ARM than x86 in this case?

Never did figure out what gcc's problem was.

https://godbolt.org/z/Y75qnTGdr

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

#18
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.

Yep. Adding "-C target-cpu=native" to rustc on my desktop computer consistently gets a ~10-15% performance boost compared to the default target. The default target is extremely conservative. As far as I can tell, it doesn't take advantage of any CPU features added in the last 20 years. (The k8 came out in 2003.)

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

#19
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.

I hope they fix it. Thats quite a basic functional unit for it to be a footgun all on its own.

Don't get your hopes up, the behavior when lo > hi is explicitly undefined.

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

#20
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.

Ehh, not so much inaccurate, more of a "floating point numbers are tricky, let's act like they aren't". Compilers are pretty skittish about changing the order of floating point operations (for good reason) and ffast-math is the thing that lets them transform equations to try and generate faster code. IE, instead of doing "n / 10" doing "n * 0.1". The issue, of course, being that things like 0.1 can't be perfectly rep…

I've never understood why generating exceptions is preferable to just using higher precision.
Post reply on HN