Skip to content

Commit faa2457

Browse files
committed
avoid method proliferation for Tuple functions
* Introducing new types and methods for a callable can invalidate already compiled method instances of a function for which world-splitting is enabled (`max_methods`). * Invalidation of sysimage or package precompiled code worsens latency due to requiring recompilation. * Lowering the `max_methods` setting for a function often causes inference issues for existing code that is not completely type-stable (which is a lot of code). In many cases this is easy to fix by avoiding method proliferation, such as by merging some methods and introducing branching into the merged method. This PR aims to fix the latter issue for some `Tuple`-related methods of some functions where decreasing `max_methods` might be interesting. Seeing as branching was deliberately avoided in the bodies of many of these methods, I opted for the approach of introducing local functions which preserve the dispatch logic as before, without branching. Thus there should be no regressions, except perhaps because of changed inlining costs. This PR is a prerequisite for PRs which try to decrease `max_methods` for select functions, such as PR: * JuliaLang#59377
1 parent 100ef9d commit faa2457

File tree

2 files changed

+82
-43
lines changed

2 files changed

+82
-43
lines changed

base/essentials.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,14 @@ julia> Base.tail(())
533533
ERROR: ArgumentError: Cannot call tail on an empty tuple.
534534
```
535535
"""
536-
tail(x::Tuple) = argtail(x...)
537-
tail(::Tuple{}) = throw(ArgumentError("Cannot call tail on an empty tuple."))
536+
function tail(x::Tuple)
537+
f(x::Tuple) = argtail(x...)
538+
function f(::Tuple{})
539+
@noinline
540+
throw(ArgumentError("Cannot call tail on an empty tuple."))
541+
end
542+
f(x)
543+
end
538544

539545
function unwrap_unionall(@nospecialize(a))
540546
@_foldable_meta

base/tuple.jl

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,14 @@ end
263263

264264
@eval split_rest(t::Tuple, n::Int, i=1) = ($(Expr(:meta, :aggressive_constprop)); (t[i:end-n], t[end-n+1:end]))
265265

266-
# Use dispatch to avoid a branch in first
267-
first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty"))
268-
first(t::Tuple) = t[1]
266+
function first(t::Tuple)
267+
f(t::Tuple) = t[1]
268+
function f(::Tuple{})
269+
@noinline
270+
throw(ArgumentError("tuple must be non-empty"))
271+
end
272+
f(t)
273+
end
269274

270275
# eltype
271276

@@ -577,52 +582,74 @@ function _eq(t1::Any32, t2::Any32)
577582
end
578583

579584
const tuplehash_seed = UInt === UInt64 ? 0x77cfa1eef01bca90 : 0xf01bca90
580-
hash(::Tuple{}, h::UInt) = h tuplehash_seed
581-
hash(t::Tuple, h::UInt) = hash(t[1], hash(tail(t), h))
582-
function hash(t::Any32, h::UInt)
583-
out = h tuplehash_seed
584-
for i = length(t):-1:1
585-
out = hash(t[i], out)
585+
function hash(t::Tuple, h::UInt)
586+
f(::Tuple{}, h::UInt) = h tuplehash_seed
587+
f(t::Tuple, h::UInt) = hash(t[1], hash(tail(t), h))
588+
function f(t::Any32, h::UInt)
589+
out = h tuplehash_seed
590+
for i = length(t):-1:1
591+
out = hash(t[i], out)
592+
end
593+
return out
586594
end
587-
return out
595+
f(t, h)
588596
end
589597

590-
<(::Tuple{}, ::Tuple{}) = false
591-
<(::Tuple{}, ::Tuple) = true
592-
<(::Tuple, ::Tuple{}) = false
593598
function <(t1::Tuple, t2::Tuple)
594-
a, b = t1[1], t2[1]
595-
eq = (a == b)
596-
if ismissing(eq)
597-
return missing
598-
elseif !eq
599-
return a < b
600-
end
601-
return tail(t1) < tail(t2)
602-
end
603-
function <(t1::Any32, t2::Any32)
604-
n1, n2 = length(t1), length(t2)
605-
for i = 1:min(n1, n2)
606-
a, b = t1[i], t2[i]
599+
f(::Tuple{}, ::Tuple{}) = false
600+
f(::Tuple{}, ::Tuple) = true
601+
f(::Tuple, ::Tuple{}) = false
602+
function f(t1::Tuple, t2::Tuple)
603+
a, b = t1[1], t2[1]
607604
eq = (a == b)
608605
if ismissing(eq)
609606
return missing
610607
elseif !eq
611-
return a < b
608+
return a < b
612609
end
610+
return tail(t1) < tail(t2)
613611
end
614-
return n1 < n2
612+
function f(t1::Any32, t2::Any32)
613+
n1, n2 = length(t1), length(t2)
614+
for i = 1:min(n1, n2)
615+
a, b = t1[i], t2[i]
616+
eq = (a == b)
617+
if ismissing(eq)
618+
return missing
619+
elseif !eq
620+
return a < b
621+
end
622+
end
623+
return n1 < n2
624+
end
625+
f(t1, t2)
615626
end
616627

617-
isless(::Tuple{}, ::Tuple{}) = false
618-
isless(::Tuple{}, ::Tuple) = true
619-
isless(::Tuple, ::Tuple{}) = false
620-
621628
"""
622629
isless(t1::Tuple, t2::Tuple)
623630
624631
Return `true` when `t1` is less than `t2` in lexicographic order.
625632
"""
633+
function isless(t1::Tuple, t2::Tuple)
634+
f(::Tuple{}, ::Tuple{}) = false
635+
f(::Tuple{}, ::Tuple) = true
636+
f(::Tuple, ::Tuple{}) = false
637+
function f(t1::Tuple, t2::Tuple)
638+
a, b = t1[1], t2[1]
639+
isless(a, b) || (isequal(a, b) && isless(tail(t1), tail(t2)))
640+
end
641+
function f(t1::Any32, t2::Any32)
642+
n1, n2 = length(t1), length(t2)
643+
for i = 1:min(n1, n2)
644+
a, b = t1[i], t2[i]
645+
if !isequal(a, b)
646+
return isless(a, b)
647+
end
648+
end
649+
return n1 < n2
650+
end
651+
f(t1, t2)
652+
end
626653
function isless(t1::Tuple, t2::Tuple)
627654
a, b = t1[1], t2[1]
628655
isless(a, b) || (isequal(a, b) && isless(tail(t1), tail(t2)))
@@ -640,8 +667,11 @@ end
640667

641668
## functions ##
642669

643-
isempty(x::Tuple{}) = true
644-
isempty(@nospecialize x::Tuple) = false
670+
function isempty(x::Tuple)
671+
f(x::Tuple{}) = true
672+
f(@nospecialize x::Tuple) = false
673+
f(x)
674+
end
645675

646676
revargs() = ()
647677
revargs(x, r...) = (revargs(r...)..., x)
@@ -679,11 +709,14 @@ empty(@nospecialize x::Tuple) = ()
679709
foreach(f, itr::Tuple) = foldl((_, x) -> (f(x); nothing), itr, init=nothing)
680710
foreach(f, itr::Tuple, itrs::Tuple...) = foldl((_, xs) -> (f(xs...); nothing), zip(itr, itrs...), init=nothing)
681711

682-
circshift((@nospecialize t::Union{Tuple{},Tuple{Any}}), @nospecialize _::Integer) = t
683-
circshift(t::Tuple{Any,Any}, shift::Integer) = iseven(shift) ? t : reverse(t)
684-
function circshift(x::Tuple{Any,Any,Any,Vararg{Any,N}}, shift::Integer) where {N}
685-
@inline
686-
len = N + 3
687-
j = mod1(shift, len)
688-
ntuple(k -> getindex(x, k-j+ifelse(k>j,0,len)), Val(len))::Tuple
712+
function circshift(t::Tuple, shift::Integer)
713+
f((@nospecialize t::Union{Tuple{},Tuple{Any}}), @nospecialize _::Integer) = t
714+
f(t::Tuple{Any,Any}, shift::Integer) = iseven(shift) ? t : reverse(t)
715+
function f(x::Tuple{Any,Any,Any,Vararg{Any,N}}, shift::Integer) where {N}
716+
@inline
717+
len = N + 3
718+
j = mod1(shift, len)
719+
ntuple(k -> getindex(x, k-j+ifelse(k>j,0,len)), Val(len))::Tuple
720+
end
721+
f(t, shift)
689722
end

0 commit comments

Comments
 (0)