-
-
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
Faster Rationals by avoiding unnecessary divgcd #35492
Conversation
82d11fe
to
3be3c81
Compare
This comment has been minimized.
This comment has been minimized.
I don't think those actually cause a failure (just scary print out) |
This comment has been minimized.
This comment has been minimized.
64fdb02
to
f659f84
Compare
Update on the constructors: the new one and only inner constructor for
The original inner constructor is now an outer constructor with the exact same specification as before. This PR is still entirely non-breaking (apart from the two bugfixes mentionned in the first message). |
Forgot to mention but the idea for |
73c5a82
to
501ce71
Compare
OK so the only CI error looks unrelated (it passes on 73c5a82 and I only fixed a docstring), and the PR is much better now with the |
To facilitate evaluating this change, (using rational.jl) I created a module |
The module is here: https://gist.github.com/JeffreySarnoff/4e13e1cb182908364694a4315b2d10ba |
Note: the module requires Julia 1.5.0-DEV. It appears that this modification provides a useful speedup for Rational multiplication/division (I get 1.15x .. 1.7x, the speedup for Rational{Int64} is more than the speedup for Rational{BigInt}, which is at the lower half of the range). It appears that this modification does not provide a useful speedup for Rational addition/subtraction. This is somewhat surprising. This probably happens because addition uses the full constructor while multiplication uses the |
@Liozou try restarting the build |
501ce71
to
303aed8
Compare
The general PR here looks good. However, we don't generally use lowercase Was it ever discussed to have leave it implemented via optional keyword argument as in |
Using |
IMHO, that makes it more confusing, because it's constructor actually creates a |
would just using type parameters be more appropriate? |
How? Functions can't accept type parameters. |
|
Yes, but its constructor's only purpose is to create a |
changing
to this seems reasonable
|
What about using a third argument instead of the type parameter for unsafe_rational(x::Integer, y::Integer) = unsafe_rational(promote(x, y)...)
unsafe_rational(x::T, y::T) where {T<:Integer} = unsafe_rational(T, x, y) |
…ment mandatory for the inner constructor
I added a commit that switches to [Just a note on the implementation detail suggested above: that sign bit does not always exist, typically for |
I wonder how widespread the use of unsigned rationals actually is in the wild... |
I don't know, but at least one PR was created specifically for |
Co-authored-by: Mustafa M <mus-m@outlook.com>
Here are some updated benchmarks for the latest version of the PR, using the benchmarking protocol of @JeffreySarnoff at #35492 (comment). The updated files are RelativeSpeed.jl (no update needed), Fractions.jl and BenchmarkingRationals.jl. The PR is tested against master (commit f1d10e7). The results are similar to previous except for matrix operations where they are better, and for relspeed(x->x[1] + x[2], rationals2, fractions2) # 1.0
relspeed(x->x[1] - x[2], rationals2, fractions2) # 1.0
relspeed(x->x[1] * x[2], rationals2, fractions2) # 1.6
relspeed(x->x[1] / x[2], rationals2, fractions2) # 1.6
relspeed(inv, rationals1, fractions1) # 16772 (!)
relspeed(x -> 2 .+ x, rationals256, fractions256) # 9.8
relspeed(x -> x .- 1, rationals256, fractions256) # 9.4
relspeed(x -> 3 .* x, rationals256, fractions256) # 2.0
relspeed(x -> x ./ 4, rationals256, fractions256) # 2.7
relspeed(inv, rationals16x16, fractions16x16) # 1.6
relspeed(inv, rationals4x4, fractions4x4) # 1.3
relspeed(inv, rationals2x2, fractions2x2) # 1.3
relspeed(x->inv.(x), rationals4, fractions4) # 2.7
relspeed(x->inv.(x), rationals16, fractions16) # 6.3
relspeed(x->inv.(x), rationals64, fractions64) # 9.8
relspeed(x->inv.(x), rationals256, fractions256) # 8.2
relspeed(prod, rationals4, fractions4) # 1.6
relspeed(prod, rationals16, fractions16) # 1.7
relspeed(prod, Rational{Int128}.(rationals64), Fraction{Int128}.(fractions64)) # 1.4
relspeed(prod, Rational{BigInt}.(rationals256), Fraction{BigInt}.(fractions256)) # 1.3
relspeed(dot, rationals4, rationals4, fractions4, fractions4) # 1.2
relspeed(dot, Rational{BigInt}.(rationals16), Rational{BigInt}.(rationals16),
Fraction{BigInt}.(fractions16), Fraction{BigInt}.(fractions16)) # 1.1
relspeed(/, rationals16x16, rationals16x16, fractions16x16, fractions16x16) # 1.3
relspeed(/, rationals4x4, rationals4x4, fractions4x4, fractions4x4) # 1.3
relspeed(\, rationals16x16, rationals16x16, fractions16x16, fractions16x16) # 1.3
relspeed(\, rationals4x4, rationals4x4, fractions4x4, fractions4x4) # 1.2
relspeed(*, rationals16x16, rationals16x16, fractions16x16, fractions16x16) # 1.1
relspeed(*, rationals4x4, rationals4x4, fractions4x4, fractions4x4) # 1.1
relspeed(+, rationals16x16, rationals16x16, fractions16x16, fractions16x16) # 1.0
relspeed(+, rationals4x4, rationals4x4, fractions4x4, fractions4x4) # 1.0 The difference for the matrix operations comes from an error in the previous benchmark since Anyway, it's all above 1 and sometimes significantly so, that's good! Of course I would love for this PR to be merged in time for 1.5, but let me know if there is anything else I can do. |
Am I correct that as it stands, this PR doesn't change the public API of Rationals at all? It seems like it is, at this point, purely a performance improvement, which is great to have. I'm not entirely sure about the name |
What about |
@StefanKarpinski That's correct, there is no change to the public API of Rationals whatsoever. |
There is also |
I've merged. We can discuss the internal name separately. Thanks for the speedup! |
You're welcome, thank you for merging! |
This PR prevents
divgcd
from being unnecessarily computed when creating aRational
with numerators and denominators that are known to be coprime. This is entirely non-breaking.Addresses #11522
The history
(#8672)
The implementation
I add two constructors toRational
that take the numerator, the denominator, and eitherVal(false)
orVal(true)
. The original constructor is left unchanged (that would be breaking).The two new constructors do not reduce the fraction to its irreducible form. Since theRational
infrastructure relies on that fact however, using any of these two new constructors to create an ill-formed rational (one whose numerator and denominator are not coprime) results in undefined behaviour. I did not document the new constructors for this very reason, but if you believe they should be exported alongside the warning I can add some documentation.The difference between the two constructors is that the one withVal(true)
performs no check whatsoever, whereas the one withVal(false)
still checks whether the denominator is negative and errors if it is equal totypemin(T)
(to conform to #32569).EDIT: see discussion below. Intermediate version withunsafe_rational
at #35492 (comment). Current version with keyword arguments at #35492 (comment). Other propositions: #35492 (comment) and #35492 (comment)EDIT: See current state of the PR at #35492 (comment). This PR is non-breaking and does not add any new export.
The new constructor is used to remove some unnecessary checks in the existing code of
rational.jl
.The idea for the implementation comes from @JeffreySarnoff's FastRationals.jl. But this PR does not provide any additional speed from relaxing the constraint that the numerator and denominator should be coprime.
The silly benchmark
EDIT: For more serious benchmarking work, see #35492 (comment)
I am using the exact same function as in #35444. This shows the new improvement compared to current master (on which #35444 has been merged).
The bonus
This PR also incidentally fixes (what I believe to be) two bugs:
rationalize(T, x)
withT<:Unsigned
andx<0
used to yieldone(T)//zero(T)
aka infinity. It now errors withOverflowError: cannot negate unsigned number
.typemin(Rational{T})
withT<:Unsigned
used to beone(T)//zero(T)
(infinity again), it is nowzero(T)//one(T)
.