Skip to content

Commit 2b7a231

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`, such as PRs: * JuliaLang#59091 * JuliaLang#59377
1 parent 100ef9d commit 2b7a231

File tree

2 files changed

+81
-43
lines changed

2 files changed

+81
-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: 73 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,13 @@ 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+
end
269273

270274
# eltype
271275

@@ -577,52 +581,74 @@ function _eq(t1::Any32, t2::Any32)
577581
end
578582

579583
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)
584+
function hash(t::Tuple, h::UInt)
585+
f(::Tuple{}, h::UInt) = h tuplehash_seed
586+
f(t::Tuple, h::UInt) = hash(t[1], hash(tail(t), h))
587+
function f(t::Any32, h::UInt)
588+
out = h tuplehash_seed
589+
for i = length(t):-1:1
590+
out = hash(t[i], out)
591+
end
592+
return out
586593
end
587-
return out
594+
f(t, h)
588595
end
589596

590-
<(::Tuple{}, ::Tuple{}) = false
591-
<(::Tuple{}, ::Tuple) = true
592-
<(::Tuple, ::Tuple{}) = false
593597
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]
598+
f(::Tuple{}, ::Tuple{}) = false
599+
f(::Tuple{}, ::Tuple) = true
600+
f(::Tuple, ::Tuple{}) = false
601+
function f(t1::Tuple, t2::Tuple)
602+
a, b = t1[1], t2[1]
607603
eq = (a == b)
608604
if ismissing(eq)
609605
return missing
610606
elseif !eq
611-
return a < b
607+
return a < b
612608
end
609+
return tail(t1) < tail(t2)
613610
end
614-
return n1 < n2
611+
function f(t1::Any32, t2::Any32)
612+
n1, n2 = length(t1), length(t2)
613+
for i = 1:min(n1, n2)
614+
a, b = t1[i], t2[i]
615+
eq = (a == b)
616+
if ismissing(eq)
617+
return missing
618+
elseif !eq
619+
return a < b
620+
end
621+
end
622+
return n1 < n2
623+
end
624+
f(t1, t2)
615625
end
616626

617-
isless(::Tuple{}, ::Tuple{}) = false
618-
isless(::Tuple{}, ::Tuple) = true
619-
isless(::Tuple, ::Tuple{}) = false
620-
621627
"""
622628
isless(t1::Tuple, t2::Tuple)
623629
624630
Return `true` when `t1` is less than `t2` in lexicographic order.
625631
"""
632+
function isless(t1::Tuple, t2::Tuple)
633+
f(::Tuple{}, ::Tuple{}) = false
634+
f(::Tuple{}, ::Tuple) = true
635+
f(::Tuple, ::Tuple{}) = false
636+
function f(t1::Tuple, t2::Tuple)
637+
a, b = t1[1], t2[1]
638+
isless(a, b) || (isequal(a, b) && isless(tail(t1), tail(t2)))
639+
end
640+
function f(t1::Any32, t2::Any32)
641+
n1, n2 = length(t1), length(t2)
642+
for i = 1:min(n1, n2)
643+
a, b = t1[i], t2[i]
644+
if !isequal(a, b)
645+
return isless(a, b)
646+
end
647+
end
648+
return n1 < n2
649+
end
650+
f(t1, t2)
651+
end
626652
function isless(t1::Tuple, t2::Tuple)
627653
a, b = t1[1], t2[1]
628654
isless(a, b) || (isequal(a, b) && isless(tail(t1), tail(t2)))
@@ -640,8 +666,11 @@ end
640666

641667
## functions ##
642668

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

646675
revargs() = ()
647676
revargs(x, r...) = (revargs(r...)..., x)
@@ -679,11 +708,14 @@ empty(@nospecialize x::Tuple) = ()
679708
foreach(f, itr::Tuple) = foldl((_, x) -> (f(x); nothing), itr, init=nothing)
680709
foreach(f, itr::Tuple, itrs::Tuple...) = foldl((_, xs) -> (f(xs...); nothing), zip(itr, itrs...), init=nothing)
681710

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
711+
function circshift(t::Tuple, shift::Integer)
712+
f((@nospecialize t::Union{Tuple{},Tuple{Any}}), @nospecialize _::Integer) = t
713+
f(t::Tuple{Any,Any}, shift::Integer) = iseven(shift) ? t : reverse(t)
714+
function f(x::Tuple{Any,Any,Any,Vararg{Any,N}}, shift::Integer) where {N}
715+
@inline
716+
len = N + 3
717+
j = mod1(shift, len)
718+
ntuple(k -> getindex(x, k-j+ifelse(k>j,0,len)), Val(len))::Tuple
719+
end
720+
f(t, shift)
689721
end

0 commit comments

Comments
 (0)