-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
make just one MethodTable #58131
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
make just one MethodTable #58131
Conversation
This doesn't seem that awful. Would obviously speed up |
Wow, did not expect this to get implemented so fast! Benchmarking this will be interesting (by which I mean, I hope it's boring :) ). Agree that keeping lists of subtypes seems ok. Though I'm sure someone out there is constructing arbitrarily-long chains of subtypes in a loop 😂
I do think we'll need this; I'm pretty sure people use this feature. If it helps, we can shard the global method table based on some properties of the signature; no reason the single method table actually has to be a single MethodTable object. |
6467c56
to
5f6efef
Compare
5f6efef
to
c4d3ce8
Compare
Ensure a total split of constructors and non-constructors, so they do not end up seaching the opposing table. Instead cache all kind lookups as keyed by typename(Type). Not really needed on its own, but it avoids a minor performance regression in #58131.
Ensure a total split of constructors and non-constructors, so they do not end up seaching the opposing table. Instead cache all kind lookups as keyed by typename(Type). Not really needed on its own, but it avoids a minor performance regression in JuliaLang#58131.
0e51bdf
to
898e1c1
Compare
Most of the complexity here is because of the need to provide legacy support to the experimental external/overlay MethodTable, otherwise this would just be a simple renaming of Table->Cache, which would then have simply been unnecessary and made this PR very tiny. Oh well.
Reflects current status of JuliaLang/julia#58131 PR.
Reflects current status of JuliaLang/julia#58131 PR.
898e1c1
to
98a18ab
Compare
JuliaTesting/Aqua.jl#334 mentions that this is supposed to be part of julia 1.12. I find that a bit surprising, given that it is already pretty late in the release cycle and this PR doesn't have a backport label. Could someone clarify that for me? Thanks |
This PR helps compile time regressions, which everyone has been very unhappy about. That's why it's going into the release even though it's so late in the process. |
It is more of an internal correctness fix for the bindings work, when brings along some slight new features (or possibility of new features), than a performance fix (which should be about neutral with it) |
Instead of hiding the fragments of the method table in each TypeName, make one variable `Core.GlobalMethods` with access to all methods. The need to split them early for performance apparently had been long past since kwargs and constructors were already using a single table and cache anyways. Some new concepts introduced here: - A single Method can now be added to multiple functions. So instead of using eval in a for loop, we could define it just once (see example below). - Several fields (`max_args`, `name`, and `backedges`) were moved from MethodTable to their TypeName. - TypeName currently has a (user-modifiable) field called `singletonname`. If set to something other than `name`, it may be used for pretty printing of a singleton object using its "canonical" (unmangled) name, particularly for `function`. - `Core.Builtin` method table entries are even more normal now, with valid `sig` fields, and special logic to specifically prevent adding methods which would become ambiguous with them (as that would violate the tfuncs we have for them). - `Core.GlobalMethods` is a `Base.Experimental.@MethodTable GlobalMethods`. - Each `MethodTable` contains a separate `MethodCache` object for managing fast dispatch lookups. We may want to use this for the `Method` field containing the `invokes` list so that lookups there get more of the same optimizations as global calls. - Methods could be put into any number of different MethodTables (or none). The `Method.primary_world` field is intended to reflect whether it is currently put into the GlobalMethods table, and what world to use in the GlobalMethods table for running its generator, and otherwise is meaningless. - The lock for TypeName backedges is a single global lock now, in `Core.GlobalMethods.mc`. - The `backedges` in TypeName are stored on the "top-most" typename in the hierarchy, to enable efficient lookup (although we might want to consider replacing this entirely with a TypeMap). The "top-most" typename is the typename of the type closest to Any, after union-splitting, which doesn't have an intersection with Builtin (so Function and Any by implication continue to not require scanning for missing backedges since it is not permitted to add a Method applicable to all functions). - Support for having backedges from experimental method tables was removed since it was unsound and had been already replaced with staticdata.jl several months ago. - Documentation lookup for `IncludeInto` is fixed (previously attached only to `Main.include` instead of all `include` functions). Example: given this existing code in base/operators: for op in (:+, :*, :&, :|, :xor, :min, :max, :kron) @eval begin ($op)(a, b, c, xs...) = (@inline; afoldl($op, ($op)(($op)(a,b),c), xs...)) end end It could now instead be equivalently written as: let ops = Union{typeof(+), typeof(*), typeof(&), typeof(|), typeof(xor), typeof(min), typeof(max), typeof(kron)} (op::ops)(a, b, c, xs...) = (@inline; afoldl(op, (op)((op)(a,b),c), xs...)) end Fixes JuliaLang#57560
Missed updates from early designs in #58131.
Is there a way to access the relevant method table from a |
There is only one MethodTable, but a MethodList is not associated with any particular table |
Needs a manual backport to 1.12 |
Instead of hiding the fragments of the method table in each TypeName, make one variable `Core.GlobalMethods` with access to all methods. The need to split them early for performance apparently had been long past since kwargs and constructors were already using a single table and cache anyways. Some new concepts introduced here: - A single Method can now be added to multiple functions. So instead of using eval in a for loop, we could define it just once (see example below). - Several fields (`max_args`, `name`, and `backedges`) were moved from MethodTable to their TypeName. - TypeName currently has a (user-modifiable) field called `singletonname`. If set to something other than `name`, it may be used for pretty printing of a singleton object using its "canonical" (unmangled) name, particularly for `function`. - `Core.Builtin` method table entries are even more normal now, with valid `sig` fields, and special logic to specifically prevent adding methods which would become ambiguous with them (as that would violate the tfuncs we have for them). - `Core.GlobalMethods` is a `Base.Experimental.@MethodTable GlobalMethods`. - Each `MethodTable` contains a separate `MethodCache` object for managing fast dispatch lookups. We may want to use this for the `Method` field containing the `invokes` list so that lookups there get more of the same optimizations as global calls. - Methods could be put into any number of different MethodTables (or none). The `Method.primary_world` field is intended to reflect whether it is currently put into the GlobalMethods table, and what world to use in the GlobalMethods table for running its generator, and otherwise is meaningless. - The lock for TypeName backedges is a single global lock now, in `Core.GlobalMethods.mc`. - The `backedges` in TypeName are stored on the "top-most" typename in the hierarchy, to enable efficient lookup (although we might want to consider replacing this entirely with a TypeMap). The "top-most" typename is the typename of the type closest to Any, after union-splitting, which doesn't have an intersection with Builtin (so Function and Any by implication continue to not require scanning for missing backedges since it is not permitted to add a Method applicable to all functions). - Support for having backedges from experimental method tables was removed since it was unsound and had been already replaced with staticdata.jl several months ago. - Documentation lookup for `IncludeInto` is fixed (previously attached only to `Main.include` instead of all `include` functions). Example: given this existing code in base/operators: for op in (:+, :*, :&, :|, :xor, :min, :max, :kron) @eval begin ($op)(a, b, c, xs...) = (@inline; afoldl($op, ($op)(($op)(a,b),c), xs...)) end end It could now instead be equivalently written as: let ops = Union{typeof(+), typeof(*), typeof(&), typeof(|), typeof(xor), typeof(min), typeof(max), typeof(kron)} (op::ops)(a, b, c, xs...) = (@inline; afoldl(op, (op)((op)(a,b),c), xs...)) end Fixes #57560 (cherry picked from commit 1735d8f)
Needs manual backport to v1.12 NEWS. xref: * issue JuliaLang#54620 * PR JuliaLang#58131
Most of the complexity here is because of the need to provide legacy support to the experimental external/overlay MethodTable, otherwise this would likely just be a simple renaming of Table->Cache, which would then have simply been unnecessary and made this PR very tiny. Oh well.Fixes #57560
The remaining question is what to call this global singleton object for introspection. I currently use
Core.GlobalMethods
.the placeholder nameCore._
, but someone may object to that.The remaining failing test (in core) is due to the better optimized cache structure here being better specialized and optimized now, but not yet reimplementing the fallback we had before for that one specific case. The good news is that there is no longer any reason to limit this to that one specific case anymore, since we could now fix all similar cases too and make it generally applicable to them. The bad news is that we might need every supertype to contain a list of all of its possible subtypes so that we can efficiently walk to all of their caches, if we want to restore that case to functional.TODO:
Core._
to something else