Eliminate most invalidations from loading FixedPointNumbers#35733
Eliminate most invalidations from loading FixedPointNumbers#35733
Conversation
| """ | ||
| function convert end | ||
|
|
||
| convert(::Type{Union{}}, x::Union{}) = throw(MethodError(convert, (Union{}, x))) |
There was a problem hiding this comment.
This is my favorite method.
There was a problem hiding this comment.
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.
| 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.
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?
| 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.
| 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.
💯 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).
| """ | ||
| 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.
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.
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_invalidationlog of nearly 600 lines. In conjunction with a couple of changes to FPN (below), this reduces it to 43, of which only 17 are actualMethodInstances. 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
@evaling dedicated methods likeconvert(::Type{Int64}, x::Int64) = x.Here's the FPN diff (EDIT: new and improved):