Repository navigation
Register parent-reduce rules with the session kernels - #11092
robert3005 wants to merge 10 commits into
Conversation
1364ff7 to
99ee78b
Compare
be6bdae to
951db46
Compare
f6a5894 to
a564054
Compare
a564054 to
cc7b45d
Compare
cc7b45d to
1d85ea1
Compare
1d85ea1 to
d493f1f
Compare
d493f1f to
aa27450
Compare
aa27450 to
0adc708
Compare
0adc708 to
3894fcb
Compare
3894fcb to
5402a3c
Compare
Constructors such as slice, filter, take and the array builtins now return lazy arrays. Code that inspects the encoding of the result, stores it, or reads it row by row lost its fast paths or changed its output. Optimize the result at those sites, and add a Dict rule that keeps only the referenced value when the codes reduce to a constant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Robert Kruszewski <github@robertk.io>
365f883 to
d180ffb
Compare
d180ffb to
4752b50
Compare
Signed-off-by: Robert Kruszewski <github@robertk.io>
Signed-off-by: Robert Kruszewski <github@robertk.io>
Each encoding now registers its parent-reduce rules with the session's ArrayKernels through register_reduce_parent_rule, keyed by the parent and child encoding ids, in place of a static PARENT_RULES set on its vtable. Rules for any scalar function are keyed by AnyScalarFn::id. The vtable reduce_parent hook and ParentRuleSet are removed. The optimizer consults only the registry and skips the children of a parent that no rule is registered for. An ExecutionCtx snapshots the parent-reduce kernels when it is created, so optimizing with the context does not read the session again. The shared dictionary values of a chunked array are now pulled up by a chunked reduce rule instead of a dict parent rule. The deprecated session-less optimize now uses only the rules vortex-array registers by default, and it does not mark its result as optimized. Signed-off-by: Robert Kruszewski <github@robertk.io>
Create one execution context per sample and mark the input arrays as optimized, so the timed loop measures the slice and its reduction rather than the session lookup and the walk over the unmarked input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Robert Kruszewski <github@robertk.io>
4752b50 to
5543bce
Compare
| ParentRuleSet::lift(&CastReduceAdaptor(ALP)), | ||
| ParentRuleSet::lift(&MaskReduceAdaptor(ALP)), | ||
| ]); | ||
| /// Registers the parent-reduce rules for [`ALP`] children in the session kernels. |
There was a problem hiding this comment.
Can we remove this and other comments please?
| /// Registers the parent-reduce rules for [`DecimalByteParts`] children in the session kernels. | ||
| pub(crate) fn register_parent_rules(session: &VortexSession) { | ||
| let kernels = session.kernels(); | ||
| kernels.register_reduce_parent_rule( |
There was a problem hiding this comment.
Don't have a strong opinion, but I'd prefer kernels.register_parent_rule(Cast, DecimalByteParts, ... so that the function is parametrized on impl ScalarFnVTable instead of an Id.
| /// Rewrites `Chunked<Dict<codes_i, values>>` into `Dict<Chunked<codes_i>, values>` only when | ||
| /// every chunk is a dictionary over the exact same values array allocation. The rewrite depends | ||
| /// on every chunk at once, so it is a rule of the chunked array rather than a parent rule each | ||
| /// chunk evaluates. |
| //! [`ArrayKernels`] stores the parent-reduce rules and parent-execution kernels that participate in | ||
| //! array optimization and execution without adding them to an encoding vtable. The optimizer | ||
| //! consults it for every parent-reduce rewrite, and the executor consults it for parent execution, | ||
| //! so a session can add support for an extension encoding or register a rule ahead of a built-in | ||
| //! one. When several entries are registered for the same key and kind, they are tried in | ||
| //! registration order until one applies. |
There was a problem hiding this comment.
If you're changing the description, can you please rewrite this paragraph altogether? This is a hard read for me
| #[derive(Clone, Debug, Default)] | ||
| pub(crate) struct ParentReduceKernels { | ||
| /// Kernels keyed by hashed `(parent, child)` pair. | ||
| kernels: Arc<HashMap<ReduceParentFnId, Arc<[ReduceParentKernelRef]>>>, |
There was a problem hiding this comment.
Do we need an inner Arc?
Do we need an outer Arc?
| pub type ReduceParentFn = | ||
| fn(child: &ArrayRef, parent: &ArrayRef, child_idx: usize) -> VortexResult<Option<ArrayRef>>; | ||
| /// Type-erased parent-reduce kernel stored in the session registry. | ||
| pub(crate) trait DynReduceParentKernel: Debug + Send + Sync + 'static { |
| parent: &ArrayRef, | ||
| child_idx: usize, | ||
| ) -> VortexResult<Option<ArrayRef>> { | ||
| let Some(child) = child.as_opt::<V>() else { |
There was a problem hiding this comment.
Can we match a child and a parent in one match instead of doing this separately?
| /// Registry of [`ReduceParentFn`]s, keyed by hashed `(parent, child)` pair. | ||
| type ReduceParentRegistry = ArcSwapMap<ReduceParentFnId, Arc<[ReduceParentFn]>>; | ||
| /// Registry of [`ReduceParentKernelRef`]s, keyed by hashed `(parent, child)` pair. | ||
| type ReduceParentRegistry = ArcSwapMap<ReduceParentFnId, Arc<[ReduceParentKernelRef]>>; |
There was a problem hiding this comment.
So we have an ArcSwapMap, then an Arc of slices, and each slice element is an Arc itself.
Can we remove two inner Arcs? We can use a Box of a slice of &'static dyn DynRule trait
| /// many threads at once. Borrowing this published value does not write to a shared reference | ||
| /// count, unlike taking a new snapshot of each map. |
There was a problem hiding this comment.
Please don't document the implementation in the public contract
| execute_parent: ExecuteParentRegistry, | ||
| } | ||
|
|
||
| #[derive(Debug, Default)] |
There was a problem hiding this comment.
We also have here an Arc of a pair of an ArcSwap of a structure holding Arcs themselves, and also a Mutex.
Can we get rid of Arcs? Can we get rid of mutex and use a semaphore instead?
| /// [`AnyScalarFn::id`]. | ||
| /// | ||
| /// Use [`Self::register_scalar_fn_reduce_parent_rule`] for a rule on a scalar function child. | ||
| pub fn register_reduce_parent_rule<V, R>(&self, parent: Id, child: V, rule: R) |
There was a problem hiding this comment.
Let's make it taking an impl ScalarFnVTable as first argument
| /// function array. Rules for the exact ids are tried before those registered under | ||
| /// [`AnyScalarFn::id`]. | ||
| /// | ||
| /// Use [`Self::register_scalar_fn_reduce_parent_rule`] for a rule on a scalar function child. |
There was a problem hiding this comment.
Can we rewrite this comment and/or trim it?
| //! after a child is decoded, new `reduce_parent` rules may match that were previously blocked. | ||
| //! | ||
| //! There are three public entry points on [`ArrayOptimizer`]: | ||
| //! Parent-reduce rules live in the session's [`kernels::ArrayKernels`] registry, keyed by the |
There was a problem hiding this comment.
Can we please trim this comment?
- register_reduce_parent_rule and register_scalar_fn_reduce_parent_rule take the parent vtable, such as Cast or Filter, instead of its Id. - Remove the doc comments on the register_parent_rules functions. - Keep the parent-reduce and execute-parent kernels each in one ArcSwap of a HashMap with boxed slices. Registration replaces the value with rcu. Signed-off-by: Robert Kruszewski <github@robertk.io>
Signed-off-by: Robert Kruszewski <github@robertk.io>
| let any_scalar_fn = AnyScalarFn.id(); | ||
| let parent_ids = [ | ||
| Some(parent.encoding_id()), | ||
| parent.is::<ScalarFn>().then_some(any_scalar_fn), |
There was a problem hiding this comment.
For the future work: any-any case is imo the wrong path
| if let Some(new_array) = reduce_parent(child, parent, slot_idx)? { | ||
| for (_kernel_idx, kernel) in reduce_parent_kernels.iter().enumerate() { | ||
| if let Some(new_array) = kernel.reduce_parent(child, parent, slot_idx)? { | ||
| vortex_ensure_eq!( |
There was a problem hiding this comment.
Please move this to a special #[cold] handler, this will be slow?
myrrc
left a comment
There was a problem hiding this comment.
LGTM. Can we please remove extra comments and make the function take an impl ScalarFnVtable instead of an Id so we'd have typecheck
| ) -> VortexResult<Option<ArrayRef>>; | ||
| } | ||
|
|
||
| pub(crate) type ReduceParentKernelRef = Arc<dyn DynReduceParentKernel>; |
There was a problem hiding this comment.
I'm not sure we need an Arc here. Yes, we have adapters, but given rules can have 'static lifetime, you can have a &'static dyn DynReduceParentKernel, in which case the adapter can be put in a const {} block which will also propagate 'static.
So a ref will also be a &'static dyn DynReduceParentKernel
Callers pass each rule as a const { &rule } block, and the registry keeps a &'static dyn reference to it instead of an Arc.
Signed-off-by: Robert Kruszewski <github@robertk.io>
Parent-reduce rules move from static
PARENT_RULESsets on the vtables into the session kernel registry, so the session controls all rules, as it already does for execute-parent kernels.Changes
initializefunction, throughArrayKernels::register_reduce_parent_rule. The registry keys the rules by the parent and child encoding ids. Rules for any scalar function useAnyScalarFn::id.ExecutionCtxtakes a snapshot of the parent-reduce kernels when it is created, so optimizing with the context does not read the session again.