Skip to content

Conversation

nsajko
Copy link
Member

@nsajko nsajko commented Aug 28, 2025

Motivation:

  • 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:

@nsajko nsajko force-pushed the avoid_method_proliferation_for_Tuple_functions branch 6 times, most recently from fe2422f to 89ebaa7 Compare August 28, 2025 16:28
* 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
@nsajko nsajko force-pushed the avoid_method_proliferation_for_Tuple_functions branch from 89ebaa7 to 32f6824 Compare August 28, 2025 16:51
tail(::Tuple{}) = throw(ArgumentError("Cannot call tail on an empty tuple."))
function tail(x::Tuple)
f(x::Tuple) = argtail(x...)
function f(::Tuple{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be better to use a branch for this I think.

Copy link
Member Author

@nsajko nsajko Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just here, for tail, or also for other functions? I see that first, which is very similar to tail, has a comment that says branching is not preferred:

julia/base/tuple.jl

Lines 266 to 268 in 9ef12b3

# Use dispatch to avoid a branch in first
first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty"))
first(t::Tuple) = t[1]

Although perhaps that concern is outdated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants