Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions vortex-array/src/aggregate_fn/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use crate::aggregate_fn::AggregateDTypes;
use crate::aggregate_fn::AggregateFn;
use crate::aggregate_fn::AggregateFnRef;
use crate::aggregate_fn::AggregateFnVTable;
use crate::aggregate_fn::session::AggregateFnSessionExt;
use crate::columnar::AnyColumnar;
use crate::dtype::DType;
use crate::executor::max_iterations;
Expand Down Expand Up @@ -99,16 +98,14 @@ impl<V: AggregateFnVTable> Accumulator<V> {
/// Accumulate `batch` into the partial state through kernel dispatch, the vtable's
/// short-circuit, or execution to a columnar array, without consulting cached statistics.
fn accumulate_batch(&mut self, batch: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<()> {
let session = ctx.session().clone();

// 1. Kernel registry first: a registered `(encoding, aggregate_fn)` kernel is strictly
// more specific than the vtable's `try_accumulate` short-circuit. Checking the
// registry first gives kernels for `Combined<V>` aggregates a chance to fire —
// `Combined::try_accumulate` always returns true, so a later kernel check would be
// unreachable.
{
let kernel = session
.aggregate_fns()
let kernel = ctx
.aggregate_kernels()
.find_aggregate_kernel(batch.encoding_id(), self.aggregate_fn.id());
if let Some(kernel) = kernel
&& let Some(result) = kernel.aggregate(&self.aggregate_fn, batch, ctx)?
Expand Down Expand Up @@ -144,8 +141,8 @@ impl<V: AggregateFnVTable> Accumulator<V> {
break;
}

if let Some(kernel) = session
.aggregate_fns()
if let Some(kernel) = ctx
.aggregate_kernels()
.find_aggregate_kernel(batch.encoding_id(), self.aggregate_fn.id())
&& let Some(result) = kernel.aggregate(&self.aggregate_fn, &batch, ctx)?
{
Expand Down Expand Up @@ -507,6 +504,46 @@ mod tests {
Ok(())
}

/// An execution context snapshots the aggregate kernels on first use, so a kernel registered
/// afterwards only fires through a new context.
#[test]
fn execution_ctx_snapshots_aggregate_kernels_on_first_use() -> VortexResult<()> {
static KERNEL: SentinelMeanPartialKernel = SentinelMeanPartialKernel;
let session = fresh_session();
let mut ctx = session.create_execution_ctx();

let mut acc = mean_f64_accumulator()?;
acc.accumulate(&dict_of_seven(), &mut ctx)?;
assert_eq!(
acc.flush()?
.as_struct()
.field("sum")
.unwrap()
.as_primitive()
.as_::<f64>(),
Some(7.0)
);

session
.get::<AggregateFnSession>()
.register_aggregate_kernel(Dict.id(), Some(Mean::combined().id()), &KERNEL);

acc.accumulate(&dict_of_seven(), &mut ctx)?;
assert_eq!(
acc.flush()?
.as_struct()
.field("sum")
.unwrap()
.as_primitive()
.as_::<f64>(),
Some(7.0)
);

acc.accumulate(&dict_of_seven(), &mut session.create_execution_ctx())?;
assert_eq!(acc.flush()?, sentinel_partial());
Ok(())
}

/// Kernel returns `Ok(None)` => dispatch falls through to `Combined::try_accumulate`'s
/// natural fan-out. The natural partial is `{sum: 7.0, count: 1}`.
#[test]
Expand Down
11 changes: 4 additions & 7 deletions vortex-array/src/aggregate_fn/accumulator_grouped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use crate::aggregate_fn::AggregateFn;
use crate::aggregate_fn::AggregateFnRef;
use crate::aggregate_fn::AggregateFnVTable;
use crate::aggregate_fn::DynAccumulator;
use crate::aggregate_fn::session::AggregateFnSessionExt;
use crate::arrays::ChunkedArray;
use crate::arrays::FixedSizeListArray;
use crate::arrays::ListViewArray;
Expand Down Expand Up @@ -289,12 +288,10 @@ impl<V: AggregateFnVTable> GroupedAccumulator<V> {
ctx: &mut ExecutionCtx,
) -> VortexResult<()> {
let mut elements = groups.elements().clone();
let session = ctx.session().clone();

for _ in 0..max_iterations() {
// Try a registered grouped kernel for the current element encoding.
if let Some(kernel) = session
.aggregate_fns()
if let Some(kernel) = ctx
.aggregate_kernels()
.find_grouped_encoding_kernel(elements.encoding_id(), self.aggregate_fn.id())
{
// SAFETY: we assume that elements execution is safe
Expand All @@ -307,8 +304,8 @@ impl<V: AggregateFnVTable> GroupedAccumulator<V> {
}

// Try a grouped kernel for the current aggregate regardless of element encoding.
if let Some(kernel) = session
.aggregate_fns()
if let Some(kernel) = ctx
.aggregate_kernels()
.find_grouped_kernel(self.aggregate_fn.id())
{
// SAFETY: we preserve the grouped shape and validity while replacing the
Expand Down
72 changes: 66 additions & 6 deletions vortex-array/src/aggregate_fn/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use vortex_session::ArcSwapMap;
use vortex_session::SessionExt;
use vortex_session::SessionGuard;
use vortex_session::SessionVar;
use vortex_utils::aliases::hash_map::HashMap;

use crate::aggregate_fn::AggregateFnId;
use crate::aggregate_fn::AggregateFnPluginRef;
Expand Down Expand Up @@ -180,12 +181,8 @@ impl AggregateFnSession {
) -> Option<&'static dyn DynAggregateKernel> {
let id = array_id.into();
let fn_id = agg_fn_id.into();
self.kernels.read(|kernels| {
kernels
.get(&(id, Some(fn_id)))
.or_else(|| kernels.get(&(id, None)))
.copied()
})
self.kernels
.read(|kernels| lookup_aggregate_kernel(kernels, id, fn_id))
}

/// Registers an aggregate kernel for an array encoding.
Expand Down Expand Up @@ -252,6 +249,69 @@ impl AggregateFnSession {
let fn_id = agg_fn_id.into();
self.grouped_encoding_kernels.insert((id, fn_id), kernel)
}

/// Captures the currently registered aggregate kernels, so repeated lookups skip the session
/// and registry loads.
pub(crate) fn kernels_snapshot(&self) -> AggregateKernels {
AggregateKernels {
kernels: self.kernels.snapshot(),
grouped_kernels: self.grouped_kernels.snapshot(),
grouped_encoding_kernels: self.grouped_encoding_kernels.snapshot(),
}
}
}

/// A point-in-time snapshot of the aggregate kernel registries of an [`AggregateFnSession`].
///
/// Lookups follow the same rules as the corresponding `find_*` methods on
/// [`AggregateFnSession`].
#[derive(Clone, Debug)]
pub(crate) struct AggregateKernels {
kernels: Arc<HashMap<AggregateKernelKey, &'static dyn DynAggregateKernel>>,
grouped_kernels: Arc<HashMap<AggregateFnId, &'static dyn DynGroupedAggregateKernel>>,
grouped_encoding_kernels:
Arc<HashMap<GroupedEncodingKernelKey, &'static dyn DynGroupedAggregateKernel>>,
}

impl AggregateKernels {
/// See [`AggregateFnSession::find_aggregate_kernel`].
pub(crate) fn find_aggregate_kernel(
&self,
array_id: ArrayId,
agg_fn_id: AggregateFnId,
) -> Option<&'static dyn DynAggregateKernel> {
lookup_aggregate_kernel(&self.kernels, array_id, agg_fn_id)
}

/// See [`AggregateFnSession::find_grouped_kernel`].
pub(crate) fn find_grouped_kernel(
&self,
agg_fn_id: AggregateFnId,
) -> Option<&'static dyn DynGroupedAggregateKernel> {
self.grouped_kernels.get(&agg_fn_id).copied()
}

/// See [`AggregateFnSession::find_grouped_encoding_kernel`].
pub(crate) fn find_grouped_encoding_kernel(
&self,
array_id: ArrayId,
agg_fn_id: AggregateFnId,
) -> Option<&'static dyn DynGroupedAggregateKernel> {
self.grouped_encoding_kernels
.get(&(array_id, agg_fn_id))
.copied()
}
}

fn lookup_aggregate_kernel(
kernels: &HashMap<AggregateKernelKey, &'static dyn DynAggregateKernel>,
array_id: ArrayId,
agg_fn_id: AggregateFnId,
) -> Option<&'static dyn DynAggregateKernel> {
kernels
.get(&(array_id, Some(agg_fn_id)))
.or_else(|| kernels.get(&(array_id, None)))
.copied()
}

/// Extension trait for accessing aggregate function session data.
Expand Down
14 changes: 14 additions & 0 deletions vortex-array/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ use vortex_session::VortexSession;
use crate::AnyCanonical;
use crate::ArrayRef;
use crate::IntoArray;
use crate::aggregate_fn::session::AggregateFnSessionExt;
use crate::aggregate_fn::session::AggregateKernels;
use crate::array::ArrayId;
use crate::builders::ArrayBuilder;
use crate::builders::builder_with_capacity_in;
Expand Down Expand Up @@ -357,6 +359,8 @@ pub struct ExecutionCtx {
// OnceLock avoids cloning the session allocator when a context does not allocate.
allocator: OnceLock<BufferAllocatorRef>,
execute_parent_kernels: Arc<ParentExecutionKernels>,
// OnceLock avoids snapshotting the aggregate kernels when a context does not aggregate.
aggregate_kernels: OnceLock<AggregateKernels>,
#[cfg(debug_assertions)]
id: usize,
#[cfg(debug_assertions)]
Expand All @@ -375,6 +379,7 @@ impl ExecutionCtx {
session,
allocator: OnceLock::new(),
execute_parent_kernels,
aggregate_kernels: OnceLock::new(),
#[cfg(debug_assertions)]
id: {
static EXEC_CTX_ID: AtomicUsize = AtomicUsize::new(0);
Expand All @@ -395,6 +400,15 @@ impl ExecutionCtx {
self.allocator.get_or_init(|| self.session.allocator())
}

/// Get the aggregate kernels for this execution context.
///
/// The session's aggregate kernel registries are snapshotted on first use. Kernels registered
/// after that are not visible to this context.
pub(crate) fn aggregate_kernels(&self) -> &AggregateKernels {
self.aggregate_kernels
.get_or_init(|| self.session.aggregate_fns().kernels_snapshot())
}

/// Set the allocator for this execution context.
pub fn with_allocator(mut self, allocator: BufferAllocatorRef) -> Self {
self.allocator = OnceLock::from(allocator);
Expand Down
Loading