-
-
Notifications
You must be signed in to change notification settings - Fork 727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide float and double versions of fmin and fmax. Improve docs. #7604
Conversation
Thanks for your pull request, @andralex! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + phobos#7604" |
Thx; @thewilsonator has taken care of this too: #7463 IMO, the main obstacle for providing all/most overloads is D's poor overload resolution, see dlang/dmd#11271 for a draft. |
I'd be glad if that were rebased and pulled then.
That's a much taller order. |
It is (was?) blocked by insufficient overload resolution breaking a Buildkite project (and probably more projects using integer literals).
The overload resolution wrt. integer literals seems to work better with a template, i.e., I previously preferred explicit overloads because I hate |
Now depends on #7605 |
What's the reason for the latest complication? Mixing types is probably a runtime performance killer (e.g., preventing vectorization), makes using LLVM intrinsics more complicated, and compile time probably suffers too. IMO, having to explicitly cast when really operating with mixed types is to be expected and forces the user to think about stuff like that ('accidental' double literals seem quite common). |
As far as I understand from your and others' notes, the functions needed to accept an integer. Maybe I misunderstood. I'd be glad to remove support for integrals. |
Edit: Btw, |
@kinke great, back to 1 template parameter. Thanks! |
I very much prefer your first version, without Edit: Or with
vs.
|
Ping @andralex |
Well I kinda like it with using min and max because whatever improvements we bring to those would be reflected in fmin and fmax. One counterargument is there aren't many improvements that could possibly be brought to floating point comparison. One other counterargument was made about template bloat - I very much believe we should resist subscribing to a programming-by-fear-of-bloat methodology. I'll make the change because it does at a level feel excessive to reuse a one-liner. |
Well I'd say the fear is clearly justified in this case, and min/max (and probably a lot more Phobos stuff) definitely need some work to account for the probably vast majority of trivial cases (2 operands of the same arithmetic type), the bloat is IMO embarrassing and doesn't exactly favor compiler performance (possibly something for @n8sh?). And as all of that would now have been instantiated with templated f{min,max} as opposed to the previous 'import' of a precompiled function in Phobos, this wouldn't have set a nice precedent for more templatizing of std.math. Thanks, lgtm now. |
There'd be two counterarguments to this. Per measurements done my myself and others, the real pain in templates most often comes from machine-generated recursive instantiations, not the meager instantiations created by user code. ( Second, it's just a defeatist way to go about things, a bad case of premature optimization. I remember people were worried about template bloat in C++ since like 1994. And some folks wrote and promoted libraries built with fear of template bloat (I remember a bunch of container libraries with inheritance and virtuals, oh boy) and - well they're in the dustbin of history. Nobody gives a rat's tail. And then there were folks who used templates everywhere and went from strength to strength - the standard library/STL, Boost, Eigen, you name it - and now pretty much anybody in C++ can't write a dozen lines of code without using some template. And guess what, some people kept on saying that template bloat will end up being the undoing of C++, and coders and compilers just keep on getting better and everybody's exchanging high fives. So I'm serious: don't code in fear of template bloat. It's a losing proposition. |
What's the matter with the SumType failure? |
Unrelated, fails on multiple PRs. |
Thanks, Nick. I won't make the mistake of force merging again though :o). |
Well, someone else did it this time - #7611 ;-) |
Some hard numbers with LDC v1.23 on Win64 for F fmin(F)(const F x, const F y) @safe pure nothrow @nogc
if (__traits(isFloating, F))
{
import std.math : isNaN;
if (isNaN(x)) return y;
version (Simple)
{
return y < x ? y : x;
}
else
{
import std.algorithm.comparison : min;
return min(x, y);
}
}
alias T = float;
T foo(T a, T b) { return fmin(a, b); } ~80 ms ( |
@kinke thanks for continuing what I think is an important discussion. So important, in fact, that I sat down and reproduced the measurements to get and share more insight into the matter. Hopefully we will be able to debunk a couple of myths together. (To be clear, the discussion in this PR is settled; I agree it's excessive to import a module just for a one-liner.) The numbers you posted consolidate two distinct overheads that would be good to distinguish. They are:
My baseline for the simple version is 39 ms (as It turns out, as one would probably expect, that in this case virtually the entire overhead is the sheer import of the module. To check that hypothesis, I reworked the code as follows: F fmin(F)(const F x, const F y) @safe pure nothrow @nogc
if (__traits(isFloating, F))
{
import std.math : isNaN;
if (isNaN(x)) return y;
version (Simple)
{
return y < x ? y : x;
}
else
{
import std.algorithm.comparison : min;
return y < x ? y : x;
}
}
alias T = float;
T foo(T a, T b) { return fmin(a, b); } i.e. the non-simple version does the import but uses nothing from it. Time: 49 ms, virtually indistinguishable from the version that instantiates templates. So importing a module has overhead. But it is understood and scales well, and that's why we don't go around and tell people to avoid imports and copypaste code instead. Within the context of actually using I should add numbers for memory consumption (as
So the overhead of instantiating straightforward templates is hardly a blip on the radar. Of course we know that scales poorly for recursive templates. This all seems to support the basic point: we should not tell people to not use templates, just the same as we should not tell people not to import modules. |
You're right, and I was aware of the obvious import factor and maybe should have emphasized that it's not just about the template bloat, but the potential extra import (when using
Just to be clear, I've never suggested avoiding the min/max template per se, but tried to show that it's not optimized for the trivial case of 2 operands with the same arithmetic type, instantiating a lot of other extra templates (edit: incl. further importing |
@kinke yes, thanks very much for these insights! Independently of all (and ironically): I looked through this module and it looks like a lot of the functions ( static foreach (F; AliasSeq!(float, double, real))
F fmin(F x) { ... }
static foreach (F; AliasSeq!(float, double, real))
F fmax(F x) { ... } That would be more in keeping with the other functions, and we can use this pattern in the entire module. Cons:
Pros:
|
The biggest problem with the 3 explicit overloads I've previously preferred is the overload resolution wrt. integers (some common literals like |
Is the failure in @safe unittest {
import std.algorithm.mutation: move;
static struct NoCopy
{
@disable this(this);
}
alias MySum = SumType!NoCopy;
NoCopy lval = NoCopy();
MySum x = NoCopy();
MySum y = NoCopy();
assert(__traits(compiles, SumType!NoCopy(NoCopy())));
assert(!__traits(compiles, SumType!NoCopy(lval)));
assert(__traits(compiles, y = NoCopy()));
assert(__traits(compiles, y = move(x))); // UNREACHABLE
assert(!__traits(compiles, y = lval)); // UNREACHABLE
assert(!__traits(compiles, y = x)); // UNREACHABLE
assert(__traits(compiles, x == y)); // UNREACHABLE
} |
CC @pbackus |
Can't reproduce locally and buildkite is green now, so I guess there's no problem? |
Caused a regression in our code. |
Yah, the breakage is in this code: static foreach(f;AliasSeq!( fabs, sqrt, sin, cos, tan, asin,
acos, atan, sinh, cosh, tanh, asinh,
acosh, atanh, /*log*/ log2, log10, logb,
log1p, exp, exp2, expm1, ceil, floor,
round, lround, trunc, rint, lrint, nearbyint,
rndtol, gamma, logGamma, sgnGamma, digamma, logmdigamma,
logmdigammaInverse, erf, erfc, normalDistribution,
normalDistributionInverse, atan2, fmod, remainder, isIdentical,
fmax, fmin, gammaIncomplete, gammaIncompleteCompl, gammaIncompleteComplInverse,
))
handlers.registerHandler!(doubleOverloadOf!f); The code attempts to pick up the address of each function, including So that leaves us between a rock and hard place. Insights welcome. Somewhat independently of that, we can - and should - fix the problem on Symmetry's side. The code is very fragile at the moment and makes assumptions it oughtn't: // this assumes that exactly one double overload actually exists and is the first arg...
// safe for std.math but not in general.
private template doubleOverloadOfHelper(alias what) {
static foreach(overload; __traits(getOverloads, __traits(parent, what), __traits(identifier, what))) {
static if(is(Parameters!(overload)[0] == double)) {
//pragma(msg, "****************", __traits(identifier, what));
alias doubleOverloadOfHelper = overload;
}
}
}
// so this extra wrapper will fallback to old behavior if there isn't a double overload
private template doubleOverloadOf(alias what) {
static if(!is(typeof(doubleOverloadOfHelper!what) == void))
alias doubleOverloadOf = doubleOverloadOfHelper!what;
else
alias doubleOverloadOf = what; // best we can do for now
} |
Considering that all the projects on Buildkite passed (it shows that there likely not going to be many projects affected) and you have to fix the Symmetry code anyhow, I would just do that and move on. |
@wilzbach yes, that seems okay. |
Nobody is preventing you from doing that. Though as I mentioned, you don't want it to be main Buildkite project, because logs would be public. |
Per https://forum.dlang.org/post/ukdlpmrnwfddeofcgbot@forum.dlang.org.