-
-
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
RFC: Widen less aggressively in tmerge #23077
Conversation
base/inference.jl
Outdated
u = typejoin(typea, typeb) | ||
end | ||
if type_too_complex(u, MAX_TYPE_DEPTH) | ||
# TODO: is this needed? | ||
return Any |
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 what situations is this needed for convergence?
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 helps convergence speed (large types that are changing value become very slow to work with very quickly)
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.
Ok, then I'll delete the comment.
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.
Or better still, put what @vtjnash wrote as a comment.
@nanosoldier |
end | ||
if isa(typea, Union) || isa(typeb, Union) || (isa(typea,DataType) && length(typea.parameters)>3) || | ||
(isa(typeb,DataType) && length(typeb.parameters)>3) | ||
# widen tuples faster (see #6704), but not too much, to make sure we can infer |
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.
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 assume removing this doesn't reintroduce #6704?
It doesn't, at least not the code as posted there. I didn't try to find variations that might still cause problems, though.
(it's partially handled by #21933 now, but I also don't handle Tuple correctly there – don't widen it fast enough – partially due to assuming that this code here existed)
Ah, I wasn't aware of that.
# don't let type unions get too big | ||
# TODO: something smarter, like a common supertype | ||
if unionlen(u) > MAX_TYPEUNION_LEN | ||
u = typejoin(typea, typeb) |
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.
perhaps do u = typejoin(u.a, u.b)
– it might as well get some benefit out of already having computed the union of a
and b
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.
To make this case convergent however, I think typejoin
would need to be reordered to handle Union
first (even before checking subtyping). Thus, it might be better to do foldl(typejoin, Union{}, uniontypes(u))
.
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.
Not sure I completely understand the problem... if typea <: typeb
or vice verse, we don't get here. Now if say typea
is a Union
, surely neither type.a <: typea.b
nor vice verse, so in the recursive call of typejoin
, the subtype test should always be false. Likewise for typeb
, of course. But admittedly, if say typea
is a TypeVar
and typeb
is a Union
, things get a little trickier to reason about. So no matter whether it's actually necessary, your proposed version is much easier to reason about :)
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.
Monotonicity here seems like it may depend on typejoin being associative, such that typejoin(A, typejoin(B, C))
== typejoin(typejoin(A, B), C)
, but I'm not sure that's true. And my version may actually be worse at assuming that. I think we may want typejoin(remove_unions(typea), remove_unions(typeb))
, which is what your version does.
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.
Hm, then typea <: typea′
would need to imply remove_unions(typea) <: remove_unions(typea′)
(monotonicity) and I had simply assumed remove_unions(u::Union) = typejoin(u.a, u.b)
to fulfill that... but it's not that obvious...
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 can see from the implementation that at least remove_unions(u::Union)
is typejoin(u.a, u.b)
; the case that I'm not sure about is where one of the types isn't a union (remove_unions(x::ANY) = 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.
Making typejoin
associative would be another option – I think any failure of associativity would be pretty awful. Not sure how to go about assuring that, but it climbs up the type lattice and lattice operations are, by definition, associative, so hopefully that can help.
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.
Correct me if I'm wrong, but ensuring convergence requires monotonicity in the sense that a <: a´ && b <: b´
implies tmerge(a, b) <: tmerge(a´, b´)
, right? Then indeed we cannot let typejoin
do the core work because of e.g. this:
julia> a´ = a = Ref{Int}
Ref{Int64}
julia> b = Ref{UInt}
Ref{UInt64}
julia> b´ = Ref{<:Integer}
Ref{#s1} where #s1<:Integer
julia> typejoin(a, b)
Ref
julia> typejoin(a, b´)
Ref{#s1} where #s1<:Integer
julia> a <: a´ && b <: b´
true
julia> typejoin(a, b) <: typejoin(a, b´)
false
But turns out we're already using typejoin
in tmerge
for (certain) tuples. And indeed (on master and 0.6):
julia> a´ = a = Tuple{Int32, Ref{Int}}
Tuple{Int32,Ref{Int64}}
julia> b = Tuple{Int64, Ref{UInt}}
Tuple{Int64,Ref{UInt64}}
julia> b´ = Tuple{Int64, Ref{<:Integer}}
Tuple{Int64,Ref{#s30} where #s30<:Integer}
julia> Core.Inference.tmerge(a, b)
Tuple{Signed,Ref}
julia> Core.Inference.tmerge(a, b´)
Tuple{Signed,Ref{#s30} where #s30<:Integer}
julia> a <: a´ && b <: b´
true
julia> Core.Inference.tmerge(a, b) <: Core.Inference.tmerge(a, b´)
false
Unless someone tells me I'm off track here and this is not an issue, my idea would be to first rethink typejoin
and depending on the outcome either go ahead with this PR or move in the other direction and completely remove typejoin
from tmerge
.
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, that cannot be the right interpretation of "monotonicity" in this context, there are too many cases where tmerge
violates that. Is it sufficient that the sequence of x
s obtained by repeatedly doing x = tmerge(x, ...)
is monotonic? Then obviously x >: tmerge(x, ...)
suffices, but also using typejoin
should be kosher then. @vtjnash, you brought up monotonicity, could you clarify what you meant and what potential problems with typejoin
you fear exactly?
base/inference.jl
Outdated
u = typejoin(typea, typeb) | ||
end | ||
if type_too_complex(u, MAX_TYPE_DEPTH) | ||
# TODO: is this needed? | ||
return Any |
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 helps convergence speed (large types that are changing value become very slow to work with very quickly)
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan |
Would these also fix the following issue reported in #22513 , tpermute(t::NTuple{N,T}, p::NTuple{N,Int}) where {T,N} = _tpermute((), t, p)
_tpermute(tdst::NTuple{N,T}, tsrc::NTuple{N,T}, p::NTuple{N,Int}) where {T,N} = tdst
_tpermute(tdst::NTuple{M,T}, tsrc::NTuple{N,T}, p::NTuple{N,Int}) where {T,M,N} = _tpermute((tdst...,tsrc[p[M+1]]), tsrc, p) for whicht |
dc920bc
to
32537f2
Compare
Use `typejoin` for unions growing too large instead of just returning `Any`.
32537f2
to
ebc40fd
Compare
ebc40fd
to
7a26e18
Compare
Concerning the validity of the first commit---using julia> function foo(x)
if x < 0.1
y = 0%Int8
elseif x < 0.2
y = 0%Int16
elseif x < 0.3
y = 0%Int32
else
y = 0%Int64
end
y
end
foo (generic function with 1 method)
julia> function bar(x)
if x < 0.1
y = (0%Int8,)
elseif x < 0.2
y = (0%Int16,)
elseif x < 0.3
y = (0%Int32,)
else
y = (0%Int64,)
end
y[1]
end
bar (generic function with 1 method)
julia> Core.Inference.return_types(foo, Tuple{Float64})
1-element Array{Any,1}:
Any
julia> Core.Inference.return_types(bar, Tuple{Float64})
1-element Array{Any,1}:
Signed So either the first commit (which lets |
I admit I don't have any practical use cases where this leads to a noticeable improvement in the inference result, but if nothing else, it's at least a net reduction of code. And (at least locally) the test times, which at least in part are quite inference-heavy, don't seem to be adversely affected, so why not...