-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
WIP: Introduce Overflow Checking for ^(::Integer, ::Integer) #21600
Conversation
I don't like the idea of slowing down a really primitive operation like |
Is integer pow really used that often though? The range of usable inputs is actually fairly small (particularly for say Int32). |
It seems a little inconsistent to me to only check overflow in this function. Wouldn't it be better to have a flag/environment/module where all functions are overflowed check? |
It would be, but we can't implement that in an efficient manner. This is supposed to catch a case where this happens very frequently that's implementable without terrible performance regressions. |
If it's only a 10-20% slowdown, and if it seems unlikely that future changes in CPUs will significantly widen the gap, then I'm certainly willing to consider it. I'd think we'd want a consistent policy with regards to |
end | ||
o && throw(OverflowError) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably want an instance, not the type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, already fixed locally. I just figured I'd start the policy discussion with some code and numbers ;).
I'm happy to make it always checked. |
I wonder if we could use |
"where any branch in the inner loop prevents vectorization." Do you need that? I'm not sure with LLVM, but in assembly can't you get a flag; and can you then OR them together, and check out of the loop? [Or is there a fast way to rule out overflow, something like (64-clz(a))b(64-clz(b)) : https://en.wikipedia.org/wiki/Find_first_set#Hardware_support ] |
Does anybody have an example of real code where this is performance critical? It would be good to look at some real world examples, because I suspect in a lot of cases we might be able to hoist the check anyway. |
Adding a triage label to see if we want to do this in light of discussion on discourse https://discourse.julialang.org/t/discussion-about-integer-overflow/69627 |
Probably the details of the implementation need to be revisited, but I've grown to be in favor of the overall idea. (To be conservative, we could keep small literal powers |
Yeah, I have an approach that I think will be easier to implement, and might be faster. The basic idea is to get a lower bound for |
I'm not sure this is worth it. It just seems kind of complex/random/undisciplined to check for overflow only in this case. As it is, those who want overflow to be checked at least acknowledge that we made a decision and stuck with it 🤷 |
The theory here is that powers are the one integer operation that is commonly used and commonly overflows with small-ish inputs. Also, with this implementation, the amount of overhead is approximately 0. |
We also throw |
@oscardssmith, as I understand it, your suggestion would look something like: function ^(x::IntXX, y::IntXX)
xʸ = power_by_squaring(x, y)
nbits = sizeof(x) * 8 - 1
if !((nbits-leading_zeros(x))*y < nbits && 1<<(nbits-leading_zeros(x)) < xʸ)
throw(OverflowError("some informative message"))
end
return xʸ
end It doesn't seem quite right, because it throws for |
Maybe this is stating the obvious, but... For literal exponents, the overflow check only requires two integer comparisons at runtime, since the bounds on function literal_pow_overflows(x::Int64, ::Val{p}) where {p}
p <= 1 && return false
p >= 64 && return x < -1 || x > 1
(x < (-3037000499, -2097152, -55108, -6208, -1448, -512, -234, -128, -78, -52, -38, -28,
-22, -18, -15, -13, -11, -9, -8, -8, -7, -6, -6, -5, -5, -5, -4, -4, -4, -4, -3, -3, -3, -3,
-3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2)[p-1] ||
x > (3037000499, 2097151, 55108, 6208, 1448, 511, 234, 127, 78, 52, 38, 28,
22, 18, 15, 13, 11, 9, 8, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3,
3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 1)[p-1])
end (The same method could obviously also be applied to non-literal exponents, although the table lookups would probably be too expensive.) |
I really think we should do something about overflow (since it's a very common criticism of Julia), even just for power, in some form, and I prefer A: A. Then my proposal here (made for physics code, speed-of-light is redundant): We could try this (trivial PR, I'm willing to make it), in 1.9-DEV and PkgEval it. [For small literal powers, if people want integer results, then a workaround is possible, by changing your code, or if wanted keep same literal power logic and return integers there.] B. |
@PallHaraldsson, my sense is people are broadly receptive to this, but we need someone to step up and write an optimized implementation. |
If this is implemented, then there also needs to be an easy way to opt out from checking when the overflow is intentional. My suggestion would be to let |
I think that the idea to catch common overflows is great, but this is technically breaking because Perhaps this could become part of a larger discussion on overflow behavior come 2.0. |
triage thinks this is probably too breaking for 1.x |
Just saw this was implemented and merged in a later PR |
I don't think so:
|
It's |
That's not what this PR does though. This PR turns on overflow checking by default in |
This PR attempted to do both things at once it seems, which stalled it. We can change the default later, but the majority of the PR looks like it was implemented now |
No it isn't, this is mod Int32 on 32-bit computers. So getting two different answers is problematic in itself for me, unless for in case of calculations where there's actually no truncation (and for portability you need to think of 32-bit). And this is the most dangerous operation. [Technically my argument for widening goes for *, just even better there, then no change of overflow, and <<, + and -, but you want one simple machine instruction for those, and * much less dangerous, and ^ hardly ever speed-critical.] How about widening always to Int128, to at least have a fast operation (rather than to BigInt...), and then you can mod all you want later (or cast to Float64 what you likely should have done)...? This is probably a bug (or intended for literal?):
|
I haven't updated the tests yet, mostly because we need to get a consensus on what to do.
The thinking behind this is that by far the most common case of people running into overflows is trying to do things like
10^x
, when they meant10.0^x
. Since this functionality is very well isolated, we can add checking without generally killing performance. There's a couple of details to work out though:x*x
,x*x*x
.cc @JeffBezanson @StefanKarpinski @stevengj @timholy