-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
Type-stable codegen for specialized in(v, ::Tuple)
#54026
Conversation
76719e8
to
8ccf78b
Compare
We were already fully specializing on the tuple type, as you can see here: julia> length(MethodAnalysis.methodinstances(in, (Any,Tuple)))
66 so I'm hopeful that this shouldn't be making our compilation times any worse. The one thing that I'm not sure about is what role the recursion plays here: are we going to be caching more compilation results for all the recursion intermediates, in which case this will be increasing the compiler's memory usage? Unfortunately, I don't know of any other way to generate per-element specialized code other than recursion like this. I would use If we don't want to pursue this approach, we should instead mark the original |
@test ∈(5, (5,)) | ||
@test ∉(0, (5,)) | ||
@test ∉(5, ()) | ||
end |
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 far as I can tell, there weren't any tests for in(x, ::Tuple)
before this PR, so I added some.
Tagged @aviatesk and @JeffBezanson since this touches compilation times, and I want to be sensitive to that. There's no rush on this PR; I just happened to be looking at this code for something else, and while I was there I noticed that in() was type-unstable for tuples. |
in(v, ::Tuple)
in(v, ::Tuple)
As more of a drive-by comment, I also recently wanted something like this and came up with my own |
Sorry for my delay. Please have another look! :) |
Since we were already specializing in for Tuples anyway, we should be generating code that can take advantage of the tuple type, rather than doing a generic, type-unstable traversal. Before: ```julia julia> @Btime Base.in($(5), (1, 2.0, 3, "hey", 5.2)) 53.541 ns (6 allocations: 224 bytes) false ``` After: ```julia julia> @Btime Base.in($(5), (1, 2.0, 3, "hey", 5.2)) 1.666 ns (0 allocations: 0 bytes) false ``` The new code statically unrolls the tuple comparisons, and thus also compiles away any of the comparisons that are statically known to be not equal, such as the `==(::Int, ::String)` comparison for element 4 in the example above.
Per review comment
The recursive implementation seems to cause stack overflow during inference with large tuples. It looks like we need a technique similar to what’s used in |
8269883
to
703de63
Compare
Competing PR: #54411 A problem (regression relative to master) with both PRs, although it's slightly worse with this PR than with mine, is that even in trivial cases the return type inference is pessimistic: julia> Core.Compiler.return_type(_in_tuple, Tuple{Int,Tuple{},Bool})
Union{Missing, Bool} EDIT: fixed in my PR EDIT2: the above observation wasn't valid. Making the third argument a constant, instead, looks fine: julia> f(x, t) = Base._in_tuple(x, t, false)
f (generic function with 1 method)
julia> Core.Compiler.return_type(f, Tuple{Int,Tuple{}})
Bool
julia> Core.Compiler.return_type(f, Tuple{Int,Tuple{(Int for _ ∈ 1:100)...}})
Bool |
The inference for |
Looks like we can go ahead with this. We can potentially incorporate #54411 in the future if it proves to be useful. |
function _in_tuple(x, @nospecialize(itr::Tuple), anymissing::Bool) | ||
@inline |
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.
Can you help me understand why you moved the inline annotation? Does it have any difference, or you just prefer this stylistically? Thanks!
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.
-
An
@inline
applied to a method carries less weight than an@inline
applied to code block within the method body. -
The semantics are different, when used within the method body it suggests to inline callees.
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.
BTW can you help review my linked PR, given that you're freshly acquainted with the code?
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.
They have the same effect, but @inline method def
is defined in base/expr.jl and is not available at this point of loading base/operators.jl when building system image. the version which is used without any arguments on the other hand is defined at the very early stage of sysimg build, so we can use it even before loading base/expr.jl.
Since we were already specializing in for Tuples anyway, we should be generating code that can take advantage of the tuple type, rather than doing a generic, type-unstable traversal. Before: ```julia julia> @Btime Base.in($(5), (1, 2.0, 3, "hey", 5.2)) 53.541 ns (6 allocations: 224 bytes) false ``` After: ```julia julia> @Btime Base.in($(5), (1, 2.0, 3, "hey", 5.2)) 1.666 ns (0 allocations: 0 bytes) false ``` The new code statically unrolls the tuple comparisons, and thus also compiles away any of the comparisons that are statically known to be not equal, such as the `==(::Int, ::String)` comparison for element 4 in the example above. --------- Co-authored-by: Shuhei Kadowaki <[email protected]>
Since we were already specializing in for Tuples anyway, we should be generating code that can take advantage of the tuple type, rather than doing a generic, type-unstable traversal.
Before:
After:
The new code statically unrolls the tuple comparisons, and thus also compiles away any of the comparisons that are statically known to be not equal, such as the
==(::Int, ::String)
comparison for element 4 in the example above.