Skip to content

Commit 5079a20

Browse files
Execute OnPair iteratively (#10210)
`OnPair::execute` executes its compressed children inline. This change requests its lengths, offsets, codes and dictionary offsets as primitive arrays, and its validity as Bool, through the scheduler before decoding. Widening the codes window reads an already-primitive child directly, instead of building and executing a cast array over it.
1 parent 8beb4e5 commit 5079a20

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

‎encodings/onpair/src/array.rs‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use vortex_array::EqMode;
2626
use vortex_array::ExecutionCtx;
2727
use vortex_array::ExecutionResult;
2828
use vortex_array::array_slots;
29+
use vortex_array::arrays::Primitive;
2930
use vortex_array::buffer::BufferHandle;
3031
use vortex_array::builders::ArrayBuilder;
3132
use vortex_array::builders::VarBinBuilder;
@@ -36,6 +37,8 @@ use vortex_array::dtype::OffsetBuilderPType;
3637
use vortex_array::dtype::PType;
3738
use vortex_array::match_each_integer_ptype;
3839
use vortex_array::match_each_varbin_builder;
40+
use vortex_array::require_child;
41+
use vortex_array::require_validity;
3942
use vortex_array::serde::ArrayChildren;
4043
use vortex_array::validity::Validity;
4144
use vortex_array::vtable::VTable;
@@ -601,6 +604,29 @@ impl VTable for OnPair {
601604
}
602605

603606
fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
607+
let array = require_child!(
608+
array,
609+
array.uncompressed_lengths(),
610+
OnPairSlots::UNCOMPRESSED_LENGTHS => Primitive
611+
);
612+
let array = require_child!(
613+
array,
614+
array.codes_offsets(),
615+
OnPairSlots::CODES_OFFSETS => Primitive
616+
);
617+
let array = require_child!(array, array.codes(), OnPairSlots::CODES => Primitive);
618+
// The dictionary is built from its offsets once and memoised, so its child is only
619+
// required while that has not happened.
620+
let array = if array.data().dictionary.get().is_some() {
621+
array
622+
} else {
623+
require_child!(
624+
array,
625+
array.dict_offsets(),
626+
OnPairSlots::DICT_OFFSETS => Primitive
627+
)
628+
};
629+
require_validity!(array, OnPairSlots::VALIDITY);
604630
canonicalize_onpair(array.as_view(), ctx).map(ExecutionResult::done)
605631
}
606632

‎encodings/onpair/src/canonical.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::OnPair;
3232
use crate::OnPairArraySlotsExt;
3333
use crate::array::dict_view;
3434
use crate::decode::code_boundary_at;
35-
use crate::decode::collect_widened;
35+
use crate::decode::collect_widened_range;
3636

3737
pub(super) fn canonicalize_onpair(
3838
array: ArrayView<'_, OnPair>,
@@ -111,7 +111,7 @@ impl<'a> OnPairDecodePlan<'a> {
111111
// array materialises only its own codes rather than the whole column's. The
112112
// contiguous decoder walks `codes` in order and never reads the per-row
113113
// boundaries, so an empty boundary slice is sound.
114-
let codes = collect_widened::<u16>(&array.codes().slice(code_start..code_end)?, ctx)?;
114+
let codes = collect_widened_range::<u16>(array.codes(), code_start..code_end, ctx)?;
115115
let dict = dict_view(array, ctx)?;
116116

117117
Ok(Self {

‎encodings/onpair/src/decode.rs‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
//! Helpers for turning [`OnPair`] slot children into the inputs the upstream
55
//! `onpair` decoder consumes.
66
7+
use std::ops::Range;
8+
79
use vortex_array::ArrayRef;
810
use vortex_array::ArrayView;
911
use vortex_array::ExecutionCtx;
12+
use vortex_array::arrays::Primitive;
1013
use vortex_array::arrays::PrimitiveArray;
1114
use vortex_array::builtins::ArrayBuiltins;
1215
use vortex_array::dtype::DType;
1316
use vortex_array::dtype::NativePType;
17+
use vortex_array::match_each_integer_ptype;
1418
use vortex_buffer::Buffer;
1519
use vortex_error::VortexExpect;
1620
use vortex_error::VortexResult;
@@ -26,8 +30,39 @@ pub(crate) fn collect_widened<T: NativePType>(
2630
arr: &ArrayRef,
2731
ctx: &mut ExecutionCtx,
2832
) -> VortexResult<Buffer<T>> {
33+
collect_widened_range(arr, 0..arr.len(), ctx)
34+
}
35+
36+
/// Canonicalise `range` of a slot child to the decoder's native primitive width.
37+
///
38+
/// A child that `OnPair::execute` has already required to be primitive is widened in place,
39+
/// without a cast array or a nested execution; any other child is cast and executed.
40+
pub(crate) fn collect_widened_range<T: NativePType>(
41+
arr: &ArrayRef,
42+
range: Range<usize>,
43+
ctx: &mut ExecutionCtx,
44+
) -> VortexResult<Buffer<T>> {
45+
if let Some(primitive) = arr.as_opt::<Primitive>()
46+
&& !primitive.dtype().is_nullable()
47+
{
48+
if primitive.ptype() == T::PTYPE {
49+
return Ok(primitive.into_owned().into_buffer::<T>().slice(range));
50+
}
51+
return match_each_integer_ptype!(primitive.ptype(), |P| {
52+
primitive.as_slice::<P>()[range]
53+
.iter()
54+
.map(|&value| {
55+
T::from(value).ok_or_else(|| {
56+
vortex_err!("OnPair value {value} does not fit {}", T::PTYPE)
57+
})
58+
})
59+
.collect::<VortexResult<Vec<T>>>()
60+
.map(Buffer::from)
61+
});
62+
}
2963
let dtype = DType::Primitive(T::PTYPE, arr.dtype().nullability());
3064
Ok(arr
65+
.slice(range)?
3166
.cast(dtype)?
3267
.execute::<PrimitiveArray>(ctx)?
3368
.into_buffer::<T>())

0 commit comments

Comments
 (0)