-
-
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
speed-up prevpow2/nextpow2 #21231
speed-up prevpow2/nextpow2 #21231
Conversation
Do you know if this would be covered by any performance tests in BaseBenchmarks? |
base/gmp.jl
Outdated
@@ -458,7 +475,7 @@ powermod(x::Integer, p::Integer, m::BigInt) = powermod(big(x), big(p), m) | |||
|
|||
function gcdx(a::BigInt, b::BigInt) | |||
if b == 0 # shortcut this to ensure consistent results with gcdx(a,b) | |||
return a < 0 ? (-a,-one(BigInt),zero(BigInt)) : (a,one(BigInt),zero(BigInt)) | |||
return a < 0 ? (-a,-ONE,ZERO) : (a,ONE,ZERO) |
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.
I don't think that is a good idea. Mutating the return values of gcdx(BigInt(1), BigInt(0))
should not change the behavior of gcdx
or any of the other functions that use ONE
or ZERO
.
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.
I'm not sure what to think about this, as mutation of BigInt
is not covered by the public API. For reference, there was a discussion about "alias-stability" in another issue.
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.
It has nothing to do with aliasing.
If I do a,b,c = gcdx(BigInt(1), BigInt(0))
and I do f(b)
which mutates b
(which is not forbidden, since it is mutable), then the global variable ONE
will be changed.
Again, this has nothing to do with aliasing.
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.
The topic in the link (aliasing between arguments and return values of functions) is slightly different from the problem here (aliasing between arguments and global objects), but still very connected, as it refers to the safety of mutating the return value of a function (and whether it is always safe (cf. e.g. "computing something always involves returning a new object" in that thread) or depends on the runtime value of the argument).
Again as mutating BigInt
s is not part of the public API (even if of course some code will mutate them for efficiency, as the language can not yet enforce their technical immutability), I think my aliasing code is OK, but I agree this is debatable (in particular as the expected speed-up here is not tremendous and concerns only the case of b==0
).
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.
I don't think it's OK. I don't know why you think #15546 is supporting this change here. In #15546 no decision was made about the whole aliasing thing, but people were complaining about undefined aliasing behavior, that is, runtime dependent aliasing behavior. Here you introduce yet another unnecessary suprising aliasing behavior, which will yield to very subtle bugs.
If julia wraps GMP's mpz
as BigInt
, then it should be possible to call any function from GMP with BigInt
s. With this change this is not safe anymore. You would always have to look up, if the wrap julia function is returning the newly introduced global variables. And I am not talking about public API, but also about future modifications of the GMP module.
a,b,c = gcd(BigInt(1), BigInt(0))
set_zero!(b)
This code snippet should never change the behavior of julia, but with the proposed change it will. It leads to runtime changes in all the functions calling ONE
and ZERO
.
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.
I didn't say the issue I linked to was supporting this change, I was merely referencing it as a relevant discussion for the problem you pointed at. BigInt
is considered to be conceptually (if not technically) an immutable type, and it's possible that a future version of Julia gets BigInt
implemented as immutable/struct
(cf. e.g. https://github.com/JuliaLang/julia/pull/17015/files#diff-c39f3bbf0cb1c2381243b42647d9ff1dR43), which would break the mutating code you posted above. I don't have a strong feeling about what should be the resolution here, and will gladly modify the code according to the consensus if any is reached.
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.
I would love to see something along the lines of #17015 being merged. But in this case, GMP.jl would have to be rewritten anyways (and this change here would be unnecessary).
Since such a change did not take place, the only way to write fast code for BigInt
s is to directly call the corresponding gmp functions. And this works pretty well at the moment. But with the changes here, this is more error prone.
While the speedup is negligible, with this change mutating a mutable type can change global variables and consequently the behavior of various functions.
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.
Would you be fine with return a < 0 ? (-a,-ONE,b) : (a,one(BigInt),b)
, where b == 0
, such that the return value would alias an argument?
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.
Sounds good to me.
3848185
to
25aa067
Compare
@tkelman by grepping through BaseBenchmarks, I don't see those functions covered. I could issue a PR to include them. Very roughly, the speed-up here is about 100% or 150%. |
@nanosoldier |
Your benchmark job has completed, but no benchmarks were actually executed. Perhaps your tag predicate contains mispelled tags? cc @jrevels |
25aa067
to
3f0a8c8
Compare
@nanosoldier |
Your benchmark job has completed - no performance regressions were detected. A full report can be found here. cc @jrevels |
3f0a8c8
to
2dc3a9f
Compare
Nanosoldier confirmed speed improvements. I rebased with minor updates, this would be ok to go on my side. Must this wait till after the next release? |
@@ -518,6 +534,12 @@ iszero(x::BigInt) = x == Clong(0) | |||
<(i::Integer, x::BigInt) = cmp(x,i) > 0 | |||
<(x::BigInt, f::CdoubleMax) = isnan(f) ? false : cmp(x,f) < 0 | |||
<(f::CdoubleMax, x::BigInt) = isnan(f) ? false : cmp(x,f) > 0 | |||
isneg(x::BigInt) = x.size < 0 | |||
ispos(x::BigInt) = x.size > 0 |
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.
Is it necessary to introduce these two function, looks like ispos
is only used in one location and isneg
in two locations?
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.
No not necessary at all, only I don't like this kind of lowlevel considerations when writing an algorithm; also I thought it could be used elsewhere, e.g. in "base/printf.jl".
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.
what about ispositive
and isnegative
(ref #20484 (comment))
might be better more explicitly named?
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.
Why not, but as isposdef
is already a function name, ispos
could be good enough. If your suggestion receives support, I will update.
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.
If we're adding these functions, should we also support them for Integer
, or might that come in the future if needed?
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.
👍 for writing positive
and negative
out in full.
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.
I think it would be useful indeed to define these functions more generally for Integer
, in the same way that iszero
is defined (it "may" increase performances...), but I prefer not in this PR.
I'm still a bit reluctant with writing ispositive
and isnegative
out in full: x > 0
is so much easier to write! also, what should we call x >= 0
? ispositiveoreq(x)
(or isposeq(x)
) ? I'm wondering now if it wouldn't be simpler to check for the value 0
in the implementation of <(x::BigInt, y::Int)
etc.
2dc3a9f
to
c6b7c86
Compare
Bump, this shouldn't take too long to review! |
@@ -104,7 +111,7 @@ function tryparse_internal(::Type{BigInt}, s::AbstractString, startpos::Int, end | |||
raise && throw(ArgumentError("invalid BigInt: $(repr(bstr))")) | |||
return _n | |||
end | |||
Nullable(sgn < 0 ? -z : z) | |||
Nullable(flipsign!(z, sgn)) |
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.
Should the !
be there? The existing statement does not seem to modify z
?
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.
As z
was created in the function, it "owns" this object and hence is free to mutate it for optimization, which is the main purpose of this PR: mutating BigInt
objects in place when possible to avoid expensive copies.
return a < 0 ? (-a,-one(BigInt),zero(BigInt)) : (a,one(BigInt),zero(BigInt)) | ||
if iszero(b) # shortcut this to ensure consistent results with gcdx(a,b) | ||
return a < 0 ? (-a,-ONE,b) : (a,one(BigInt),b) | ||
# we don't return the globals ONE and ZERO in case the user wants to |
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.
This comment is somewhat confusing: The comment states that you do not return ONE
. Yet one of the branches in the line immediately preceding the comment returns ONE
?
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.
In the preceding line, the return values are either one(BigInt)
or -ONE
, both of these expressions create a freshly allocated BigInt
(as requested by @thofma).
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.
Ah, the -
! Perfect. Thanks for the explanation!
Is this good to merge now? Perhaps @JeffBezanson or @simonbyrne. |
This is good to merge on my side, modulo a decision regarding |
d241467
to
b521acd
Compare
Bump |
This seems applicable to 0.6 since it doesn't change any visible behavior as far as I can tell? |
it's implementing several new methods on BigInt which weren't present in alpha, beta, or rc1 |
Besides those few (unexported) new methods on |
If the new methods implement the same behavior as before, it's not actually breaking. |
it's the imported methods that are now globally visible that weren't previously implemented at all - not breaking unless someone was relying on them not working, but a new feature |
If I'm not mistaken, only |
Avoid a library call into GMP. (cherry picked from commit 05708f8) ref #21231 speed-up prevpow2 & nextpow2 for BigInt (cherry picked from commit 42fd2a7) GMP: add flipsign[!] functions and use more iszero/isneg/ispos (cherry picked from commit f63bdfd) add ZERO & ONE global BigInt constants for optimization (cherry picked from commit b521acd)
The speed-up comes mainly from avoiding creating unnecessarily
BigInt
temporaries.The last commit introduces
ONE
andZERO
which areBigInt
constants with obvious values. The WIP here is because I don't know of a good way to initialize them: I tried toccall
thempz_set_si
function from within the__init__
function, but then get a segmentation fault when launching julia (my current workaround is to use an array, or could use a malloc). Suggestions welcome.