-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Eliminate most invalidations from loading FixedPointNumbers #35733
Conversation
@@ -166,13 +166,16 @@ true | |||
""" | |||
function convert end | |||
|
|||
convert(::Type{Union{}}, x::Union{}) = throw(MethodError(convert, (Union{}, x))) |
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 is my favorite method.
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, this PR has been an exercise in "how on earth can inference discover an ambiguity here?"
I added another couple of weird methods and got it down to 33 lines with 6 real methods. I've also been able to simplify the FPN diff; I just edited the one above to be the new version. One benchmark (Pkg is a juicy target for invalidation, it seems): julia 1.4:
So a 2.5 second hit to recompile Pkg methods after loading FixedPointNumbers. This PR:
Definitely not enough for a cup of tea. So, another package made largely "side-effect free" with respect to other functionality. Whee! |
Residual log, in case anyone has additional suggestions for improvement:
|
I have some code somewhere that skips doing invalidations if the intersection would be ambiguous. I think that could avoid the need for the |
ColorTypes only needs this change: diff --git a/src/conversions.jl b/src/conversions.jl
index b74d961..79c1014 100644
--- a/src/conversions.jl
+++ b/src/conversions.jl
@@ -70,9 +70,9 @@ end
# no-op and element-type conversions, plus conversion to and from transparency
# Colorimetry conversions are in Colors.jl
convert(::Type{C}, c::C) where {C<:Colorant} = c
-convert(::Type{C}, c) where {C<:Colorant} = cconvert(ccolor(C, typeof(c)), c)
+convert(::Type{C}, c::Colorant) where {C<:Colorant} = cconvert(ccolor(C, typeof(c)), c)
cconvert(::Type{C}, c::C) where {C} = c
-cconvert(::Type{C}, c) where {C} = _convert(C, base_color_type(C), base_color_type(c), c)
+cconvert(::Type{C}, c::Colorant) where {C} = _convert(C, base_color_type(C), base_color_type(c), c)
convert(::Type{C}, c::Color, alpha) where {C<:TransparentColor} = cconvert(ccolor(C, typeof(c)), c, alpha)
cconvert(::Type{C}, c::Color, alpha) where {C<:TransparentColor} =_convert(C, base_color_type(C), base_color_type(c), c, alpha) (plus some stuff to disable the error hints since that changed) and the total invalidation log for FixedPointNumbers + ColorTypes is down to 55 lines. This is from an original of 2053. The fact that ColorTypes has largely been fixed by the same changes made for FPN is a relief; I was beginning to worry that each package would be a big lift on its own, this is the first sign I've seen of synergy. |
I wondered about that. I figured it would be worth seeing how much this contributed to the problem; it's certainly the majority for this package. |
These changes, combined with more extensive changes to Julia itself, greatly reduce latency stemming from loading FixedPointNumbers. Ref JuliaLang/julia#35733. There will be very little benefit to this on its own, but we can at least find out if it works across Julia versions.
JuliaLang/julia#35733. This will have very little impact on its own, but it shouldn't hurt anything and it prepares the way for future gains.
@@ -447,6 +448,9 @@ Stacktrace: | |||
``` | |||
""" | |||
sizeof(x) = Core.sizeof(x) | |||
# The next two methods prevent invalidation | |||
sizeof(::Type{Union{}}) = Core.sizeof(Union{}) | |||
sizeof(::Type{T}) where T = Core.sizeof(T) |
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'll force us to generate code for each type, and forces us to add a dispatch, which I believe we don't want for it?
@@ -240,6 +244,9 @@ julia> zero(rand(2,2)) | |||
""" | |||
zero(x::Number) = oftype(x,0) | |||
zero(::Type{T}) where {T<:Number} = convert(T,0) | |||
# prevent invalidation | |||
zero(x::Union{}) = throw(MethodError(zero, (x,))) |
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.
zero(x::Union{}) = throw(MethodError(zero, (x,))) | |
zero(x::Union{}) = unreachable |
Though I can't promise that defining these might not cause other, unintended, side-effects.
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.
💯 Agreed, I would hold off on adding these --- they could cause problems, and they're the kind of thing we should be able to handle automatically (i.e. if an argument is bottom, obviously the code is unreachable).
@@ -306,25 +306,29 @@ with reduction `op` over an empty array with element type of `T`. | |||
|
|||
If not defined, this will throw an `ArgumentError`. | |||
""" | |||
reduce_empty(op, T) = _empty_reduce_error() | |||
reduce_empty(::typeof(+), T) = zero(T) | |||
reduce_empty(op, ::Type{T}) where T = _empty_reduce_error() |
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.
Style nit: I think we usually write short form functions with the braces {}
to visually distinguish them:
reduce_empty(op, ::Type{T}) where T = _empty_reduce_error() | |
reduce_empty(op, ::Type{T}) where {T} = _empty_reduce_error() |
I'd be very happy to hold off on this, especially if there's an automatic solution on the horizon. I don't have a burning need to see these littering our method tables. Happy to treat it as a proof of principle for a more systematic approach. |
JuliaLang/julia#35733. This will have very little impact on its own, but it shouldn't hurt anything and it prepares the way for future gains.
JuliaLang/julia#35733. This will have very little impact on its own, but it shouldn't hurt anything and it prepares the way for future gains.
We should be able to keep all the Edit: #35803 |
reduce_empty(op, T) = _empty_reduce_error() | ||
reduce_empty(::typeof(+), T) = zero(T) | ||
reduce_empty(op, ::Type{T}) where T = _empty_reduce_error() | ||
reduce_empty(::typeof(+), ::Type{Union{}}) = _empty_reduce_error() # avoid invalidation |
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 we should have this. It is not for avoiding invalidation but for "correctness." I think it's better to do _empty_reduce_error()
in sum(Union{}[])
than throwing the MethodError
.
These changes, combined with more extensive changes to Julia itself, greatly reduce latency stemming from loading FixedPointNumbers. Ref JuliaLang/julia#35733. There will be very little benefit to this on its own, but we can at least find out if it works across Julia versions.
Superseded by lots of other PRs |
On master (or really, on #35714), loading FixedPointNumbers ("FPN") produces a
jl_debug_method_invalidation
log of nearly 600 lines. In conjunction with a couple of changes to FPN (below), this reduces it to 43, of which only 17 are actualMethodInstance
s. Most of the remaining ones derive from invalidatingconvert(Type{T<:Int64}, Int64) where {T<:Int64}
, which gets invalidated byI have not been successful in figuring out how to head that off short of
@eval
ing dedicated methods likeconvert(::Type{Int64}, x::Int64) = x
.Here's the FPN diff (EDIT: new and improved):