Support out of order grouped aggregate accumulation - #8379
Conversation
Signed-off-by: "Nicholas Gates" <nick@nickgates.com>
Signed-off-by: "Nicholas Gates" <nick@nickgates.com>
Merging this PR will degrade performance by 57.86%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
Signed-off-by: "Nicholas Gates" <nick@nickgates.com>
I, Nicholas Gates <nick@nickgates.com>, hereby add my Signed-off-by to this commit: 6bdcd32 I, Nicholas Gates <nick@nickgates.com>, hereby add my Signed-off-by to this commit: 9bd157f I, Nicholas Gates <nick@nickgates.com>, hereby add my Signed-off-by to this commit: 50701b2 Signed-off-by: Nicholas Gates <nick@nickgates.com>
|
We wait that vortex was a linear scan engine. This breaks it correct? |
onursatici
left a comment
There was a problem hiding this comment.
I prefer this API, not only that it supports out of order groups but also it can keep the memory footprint lower by not having us to materialise each group before calling aggregate.
I think you are conflicting with #8314
Sum and count kernels you added make sense to me but we went with having a encoding agnostic kernel support on the registry on the merged PR instead of having the kernel in the aggregate function's vtable
No, the idea is that I can take an array, compute the group indices (i.e. using a scalar function), then update the aggregate state per group. This still doesn't require a shuffle / sort. But it does mean aggregate state may grow large. So in the future this is where we would want the ability to spill partials / reconstruct later. |
Signed-off-by: Nicholas Gates <nick@nickgates.com>
Signed-off-by: Nicholas Gates <nick@nickgates.com>
| num_groups: usize, | ||
| } | ||
|
|
||
| fn dense_grouped_input(values: ArrayRef, group_sizes: &[usize]) -> DenseGroupedInput { |
There was a problem hiding this comment.
nit - this is basically the constructor for DenseGroupedInput
Signed-off-by: Nicholas Gates <nick@nickgates.com>
| fn grouped_count( | ||
| elements: &ArrayRef, | ||
| group_ranges: &GroupRanges, | ||
| pub(super) fn try_accumulate_grouped( |
There was a problem hiding this comment.
nit - this seems short enough to inline into the vtable
onursatici
left a comment
There was a problem hiding this comment.
I think we should choose either to have encoding agnostic grouped kernels that we register in the registry, like develop does with AggregateFnSession::register_grouped_kernel, or have the fast path as methods on the aggregate function vtable, like this PR does with AggregateFnVTable::try_accumulate_grouped
If we merge this as is, we will have two ways that do very similar things
|
Agreed, in general I want to move everything towards session kernels, including both aggregate functions and scalar functions. |
| for (&group_id, valid) in group_ids.iter().zip(validity.iter()) { | ||
| if valid { | ||
| states[group_id as usize] += 1; | ||
| } |
There was a problem hiding this comment.
This also looks kinda slow
| fn for_each_valid_idx(validity: &Mask, len: usize, mut f: impl FnMut(usize)) { | ||
| match validity.indices() { | ||
| AllOr::All => { | ||
| for idx in 0..len { | ||
| f(idx); | ||
| } | ||
| } | ||
| AllOr::None => {} | ||
| AllOr::Some(indices) => { | ||
| for &idx in indices { | ||
| f(idx); | ||
| } |
There was a problem hiding this comment.
Is this not a method on Mask?
| &self, | ||
| _states: &mut [Self::Partial], | ||
| _batch: &ArrayRef, | ||
| _group_ids: &[u32], |
There was a problem hiding this comment.
I think we should use an ArrayRef here so we can dispatch on different encoding ids? RLE, Constant
| fn push_result(&mut self, state: ArrayRef) -> VortexResult<()> { | ||
| fn merge_group( | ||
| &mut self, | ||
| into: u32, |
There was a problem hiding this comment.
| into: u32, | |
| into_num_groups: u32, |
| group_ids: &[u32], | ||
| num_groups: usize, |
There was a problem hiding this comment.
Should this be a Group struct?
| group_ids: &[u32], | ||
| ctx: &mut ExecutionCtx, |
There was a problem hiding this comment.
Why don't we know the num_groups here?
|
This PR has been marked as stale because it has been open for 14 days with no activity. Please comment or remove the stale label if you wish to keep it active, otherwise it will be closed in 7 days |
|
This PR was closed because it has been inactive for 7 days since being marked as stale. |
Signed-off-by: Nicholas Gates <nick@nickgates.com>
Signed-off-by: Nicholas Gates <nick@nickgates.com>
Signed-off-by: Nicholas Gates <nick@nickgates.com>
…regate Signed-off-by: Nicholas Gates <nick@nickgates.com> # Conflicts: # vortex-array/benches/aggregate_grouped.rs # vortex-array/src/aggregate_fn/accumulator_grouped.rs # vortex-array/src/aggregate_fn/fns/sum/grouped.rs # vortex-array/src/aggregate_fn/fns/sum/mod.rs # vortex-array/src/scalar_fn/fns/list_sum.rs
Our original API for grouped aggregate functions only supported groups that we pre-sorted into complete lists. That meant large groups had to be globally sorted before we could start accumulating their internal state, unlike the grouped-aggregate APIs used by query engines.
This PR updates the grouped accumulator API to accept caller-assigned dense
GroupIdsdirectly.Summary
GroupIds { ids: ArrayRef, num_groups }, including repeated, out-of-order, and absent IDs.list_sumcallers.u64per group.u64,i64,f64, or decimal vectors, with overflow and empty state kept separately.i8throughi256) once when the accumulator is created.execute_scalarper row.takeper group.Spiral impact
Spiral already interns group keys into dense
u32state slots, so it can pass those IDs directly without rearranging values into ordered list groups. This covers the common count/sum machinery while allowing Spiral to retain its stronger integer/decimal mean states and other specialized paths.Local performance
Medians below use 65,536 non-null
i32rows and include canonical result materialization. Shuffled inputs contain the same multiset of IDs as their clustered counterpart.The original 65,536-row / 4,096-group shuffled sum benchmark was approximately 215 μs with per-group
SumPartialstate. Typed storage plus adaptive scatter reduces it to approximately 26.9 μs while also materializing the result and tracking SQL empty-state semantics, an approximately 8× improvement.On the existing roughly 1K-row cases, representative medians are 1.9 μs for clustered-null count, 3.7 μs for nullable all-valid
i32sum, 3.9 μs for all-validf64sum, and 6.2 μs for clustered-nulli32sum.Validation
cargo +nightly fmt --allcargo test -p vortex-array --lib(3,376 passed, 1 ignored)cargo bench -p vortex-array --bench aggregate_groupedcargo clippy -p vortex-array --all-targets --all-featurescargo clippy --all-targets --all-featuresRUSTDOCFLAGS="-D warnings" cargo doc --profile ci --no-deps -p vortex-array