-
-
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
Add a fast overload for fieldcount(Tuple{...})
#32495
Conversation
Add an overload for `Base.fieldcount` for Tuples, since we can determine it much more cheaply than for other types. Currently `fieldcount(Tuple{Int, Float32})`, for example, cannot const fold, and thus requires a runtime length check of the types array. Before this change, on Julia 1.1 this took around 6ns, and on master, it's currently 13ns: ```julia julia> @Btime fieldcount(Tuple{Int,Int}) 13.043 ns (0 allocations: 0 bytes) 2 ``` After this change, this is instantaneous: ```julia julia> @Btime fieldcount(Tuple{Int,Int}) 0.025 ns (0 allocations: 0 bytes) 2 ``` Of course, the tradeoff is that the dispatch becomes more expensive if we have to do a dynamic dispatch: ```julia julia> randtup() = tuple(1:rand(1:10)...) randtup (generic function with 1 method) julia> f() = fieldcount(typeof(randtup()))+1 f (generic function with 1 method) julia> @Btime f() 680.552 ns (8 allocations: 438 bytes) 5 julia> Base.fieldcount(@nospecialize t::Type{T}) where T<:Tuple{Vararg{Any,N}} where N = N julia> @Btime f() 703.386 ns (8 allocations: 428 bytes) 7 ```
Indeed, this would now make the function specialized on (and slow for) all types, since we can't create a single guard signature and pre-resolve the dispatch. FWIW, I'm proposing something similar at https://github.com/JuliaLang/julia/pull/32427/files#diff-fd99c9a15dadc23d9200069dadafb8bbR19, for similar reasons. |
Thanks for the quick comment, @vtjnash.
Gotcha, thanks for the explanation. that makes sense! TBH it's a bit awkward to use I think the applicable concept is |
Just commenting that whatever the solution, we should include NamedTuple in it as well (I've been meaning to bring this up as I keep seeing the same kind of poor codegen for fieldcount) |
@vtjnash I'm going back through some old notifications and came back here. :) I know that you referenced https://github.com/JuliaLang/julia/pull/32427/files#diff-fd99c9a15dadc23d9200069dadafb8bbR19, where the stated goal was explicitly to reduce usage of julia> Base.@pure fieldcount_pure_reflection(@nospecialize t::Type{<:Tuple}) = length(t.parameters)
fieldcount_pure_reflection (generic function with 1 method)
julia> @btime fieldcount_pure_reflection(Tuple{Int,Int,Int})
0.035 ns (0 allocations: 0 bytes)
3 That will not specialize new methods for new types. (And so then I think we could be flexible on whether to make this a separate function only for tuples, or to add a method to EDIT: (and for NamedTuples, of course) |
That Yeah, that's roughly what I did in #32427. Other parts of that triggered various type system bugs, so I got side-tracked. |
Add an overload for
Base.fieldcount
for Tuples, since we can determine it much more cheaply than for other types:Currently
fieldcount(Tuple{Int, Float32})
, for example, cannot const fold, and thus requires a runtime length check of the types array.Before this change, on Julia 1.1 this took around 6ns, and on master, it's currently 13ns:
After this change, this is instantaneous:
Of course, the tradeoff is that the dispatch becomes more expensive if we have to do a dynamic dispatch:
(@JeffBezanson as I was reviewing my notes from a meeting we had last year, it occurred to me that this overload might be useful. I remembered that you said
fieldcount
is still more expensive than it needs to be, and we came up with thisVararg
workaround together. Seems like that might be useful to put in base?)