-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Eliminate (almost) all invalidations by disabling world splitting #59091
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
base: master
Are you sure you want to change the base?
Conversation
…egardless of number of methods) [Using Claude]
See #48320 for an earlier attempt. Specifically #48320 (comment) for the breakage caused by imprecisely inferring the element type of empty arrays. |
This data / test is great (always good to test and confirm expectations!) FWIW, I thought about this a bit more after https://pretalx.com/juliacon-2025/talk/DCDEQV/ and I'm pretty convinced that we need this optimization.. Here's my argument: As is, the compiler when "world-splitting" a wide (largely uncovered) function call has two cases:
In the case of (1), we really want to optimize, since otherwise we are leaving free (and substantial) inference gains on the table (e.g. call returns concrete type vs. My concern is that disabling world-splitting means basically always assuming that (2) is the situation, which implies that we are pretty much always leaving substantial inference gains on the table whenever the reality is (1) |
I was thinking during your presentation that it might be better to do this deoptimization combined with the method table freezing mechanism implemented in #56143. That means optimizing with However, I think it's hard to freeze To find a good heuristic, we probably need to investigate in detail the inference conditions required to cover those packages as well. I don't think just tweaking |
@nanosoldier Mostly to see runtime of pkgeval |
Oof, 17 failures for Trim verify finished with 17 errors, 0 warnings. A lot of that would be improved by making sure we world-split for any anonymous function types (e.g. closures + inner functions for args expansion). Anonymity means that these are pretty much never extended so they should be treated as |
We need a good heuristic for
Candidates:
|
Disabling world-splitting or even just changing the heuristic is quite heavy-handed. Meanwhile, safe approaches, like outlined in a comment of mine on PR #58788, still haven't been tried. I believe I actually got a branch locally that implements that but I haven't pushed it as I was waiting on that PR getting merged. |
Furthermore, as I discuss there, setting |
@nsajko, is
*It'd be reassuring if someone could point out where that optimization takes place. |
The package evaluation job you requested has completed - possible new issues were detected. Report summary❗ Packages that crashed6 packages crashed only on the current version.
6 packages crashed on the previous version too. ✖ Packages that failed159 packages failed only on the current version.
1195 packages failed on the previous version too. ✔ Packages that passed tests31 packages passed tests only on the current version.
5274 packages passed tests on the previous version too. ~ Packages that at least loaded26 packages successfully loaded only on the current version.
3176 packages successfully loaded on the previous version too. ➖ Packages that were skipped altogether907 packages were skipped on the previous version too. |
PkgEval is both very good and very bad. I estimate just over 100 reproducible failures on this PR that are not reproducible on master (all of which, I imagine, are due to packages that depend on type inference results being stable across Julia versions). I also estimate that PkgEval (a proxy for compile time + a shred of runtime, with bias from tests that fail fast) is 3x faster on this branch than master. While preliminary results, 3x compile perf vs. breaking lots of folks (1% of our 10k registered packages) who depend on internals points to making some efforts to reduce breakage (esp. where we leak those internals from Base in returning empty arrays) and then proceeding. |
Just looked through a nonrandom sample of 7 of the new failures and 6 of them were literally testing inference results or performance or display based derivatives of inference results and one had a MethodError from a |
Just to state the obvious but consider the scenario where inference is completely turned off. That would:
But that doesn't make it a good idea. |
Yes, obviously. This PR doesn't hurt runtime of concretely typed code, though. |
Might want to benchmark JuliaInterpreter. It's already too slow, but if this makes it really too slow that would not be a good outcome. |
Here's the JuliaInterpreter.jl benchmarks
|
It's definitely a hit, but not as bad as I feared. |
Is:
a middle ground? I'm trying to understand effect of 1 vs 3 (vs 2; or 4 eliminated from PR #58788). This seems like an optimization and should not break code?! Only is breaking some bad tests/packages using internals? Anything that "fixes (almost) all invalidation" seems like a great thing (even for 1.12 if possible..., since a small change, that could easily be undone). |
Not really. There's somewhat of a qualitative difference between 1+fully covers and 2. The difference is that the first makes it so there is no invalidation without piracy, while the 2nd doesn't. |
A function which is meant to have methods added to it by users should have a small `max_methods` value, as world-splitting just leads to unnecessary invalidation in that case, in the context of the package ecosystem, where users are allowed to add arbitrarily many methods to such functions. xref PR JuliaLang#57884 xref PR JuliaLang#58788 xref PR JuliaLang#58829 xref PR JuliaLang#59091
A function which is meant to have methods added to it by users should have a small `max_methods` value, as world-splitting just leads to unnecessary invalidation in that case, in the context of the package ecosystem, where users are allowed to add arbitrarily many methods to such functions. xref PR JuliaLang#57884 xref PR JuliaLang#58788 xref PR JuliaLang#58829 xref PR JuliaLang#59091
I'm phrasing this in vague terms since I don't know precisely what it would entail, but would it make sense to provide a separate " I'm envisioning that giving a longer onramp to observe & handle any potential breakages, if the fear of disruption is too much to just put this into nightly |
* 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
A function which is meant to have methods added to it by users should have a small `max_methods` value, as world-splitting just leads to unnecessary invalidation in that case, in the context of the package ecosystem, where users are allowed to add arbitrarily many methods to such functions. xref PR JuliaLang#57884 xref PR JuliaLang#58788 xref PR JuliaLang#58829 xref PR JuliaLang#59091
Let's see if we can keep runtime performance while getting rid of a major cause of compile time performance issues.