Skip to content

Commit 3ee7fa1

Browse files
committed
Use PiecewiseSequence for List take elements
Signed-off-by: Daniel King <dan@spiraldb.com>
1 parent 80f7b89 commit 3ee7fa1

1 file changed

Lines changed: 65 additions & 119 deletions

File tree

  • vortex-array/src/arrays/list/compute

vortex-array/src/arrays/list/compute/take.rs

Lines changed: 65 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ use crate::arrays::list::ListArrayExt;
2323
use crate::arrays::piecewise_sequence::UnitMultiplierLengths;
2424
use crate::arrays::piecewise_sequence::execute_unit_multiplier_index_arrays;
2525
use crate::arrays::primitive::PrimitiveArrayExt;
26-
use crate::builders::ArrayBuilder;
27-
use crate::builders::PrimitiveBuilder;
2826
use crate::dtype::IntegerPType;
29-
use crate::dtype::Nullability;
3027
use crate::dtype::UnsignedPType;
3128
use crate::executor::ExecutionCtx;
3229
use crate::match_each_unsigned_integer_ptype;
@@ -64,7 +61,7 @@ impl TakeExecute for List {
6461
match_each_unsigned_integer_ptype!(offsets.ptype(), |O| {
6562
match_each_unsigned_integer_ptype!(indices.ptype(), |I| {
6663
match_smallest_offset_type!(total_approx, |OutputOffsetType| {
67-
_take::<I, O, OutputOffsetType>(
64+
take_with_piecewise_elements::<I, O, OutputOffsetType>(
6865
array,
6966
offsets.as_view(),
7067
indices.as_view(),
@@ -77,7 +74,11 @@ impl TakeExecute for List {
7774
}
7875
}
7976

80-
fn _take<I: IntegerPType, O: IntegerPType, OutputOffsetType: IntegerPType>(
77+
fn take_with_piecewise_elements<
78+
I: IntegerPType,
79+
O: IntegerPType,
80+
OutputOffsetType: IntegerPType,
81+
>(
8182
array: ArrayView<'_, List>,
8283
offsets_array: ArrayView<'_, Primitive>,
8384
indices_array: ArrayView<'_, Primitive>,
@@ -91,56 +92,78 @@ fn _take<I: IntegerPType, O: IntegerPType, OutputOffsetType: IntegerPType>(
9192
.vortex_expect("Failed to compute validity mask")
9293
.execute_mask(indices_array.as_ref().len(), ctx)?;
9394

94-
if !indices_validity.all_true() || !data_validity.all_true() {
95-
return _take_nullable::<I, O, OutputOffsetType>(array, offsets_array, indices_array, ctx);
96-
}
97-
9895
let offsets: &[O] = offsets_array.as_slice();
9996
let indices: &[I] = indices_array.as_slice();
10097

101-
let mut new_offsets = PrimitiveBuilder::<OutputOffsetType>::with_capacity(
102-
Nullability::NonNullable,
103-
indices.len(),
104-
);
105-
let mut elements_to_take =
106-
PrimitiveBuilder::with_capacity(Nullability::NonNullable, 2 * indices.len());
98+
let offsets_capacity = indices
99+
.len()
100+
.checked_add(1)
101+
.ok_or_else(|| vortex_err!("List take offsets length overflow"))?;
102+
let mut new_offsets = BufferMut::<OutputOffsetType>::with_capacity(offsets_capacity);
103+
let mut element_starts = BufferMut::<u64>::with_capacity(indices.len());
104+
let mut element_lengths = BufferMut::<u64>::with_capacity(indices.len());
105+
106+
let mut current_offset = 0usize;
107+
new_offsets.push(OutputOffsetType::zero());
107108

108-
let mut current_offset = OutputOffsetType::zero();
109-
new_offsets.append_zero();
109+
for (&data_idx, index_valid) in indices.iter().zip_eq(indices_validity.iter()) {
110+
if !index_valid {
111+
new_offsets.push(new_offset_value::<OutputOffsetType>(current_offset)?);
112+
element_starts.push(0);
113+
element_lengths.push(0);
114+
continue;
115+
}
110116

111-
for &data_idx in indices {
112117
let data_idx: usize = data_idx.as_();
113118

119+
if !data_validity.value(data_idx) {
120+
new_offsets.push(new_offset_value::<OutputOffsetType>(current_offset)?);
121+
element_starts.push(0);
122+
element_lengths.push(0);
123+
continue;
124+
}
125+
114126
let start = offsets[data_idx];
115127
let stop = offsets[data_idx + 1];
128+
let start: usize = start.as_();
129+
let stop: usize = stop.as_();
130+
let length = stop
131+
.checked_sub(start)
132+
.ok_or_else(|| vortex_err!("List offsets are not monotonic at offset {stop}"))?;
116133

117-
// Annoyingly, we can't turn (start..end) into a range, so we're doing that manually.
118-
//
119-
// We could convert start and end to usize, but that would impose a potentially
120-
// harder constraint - now we don't care if they fit into usize as long as their
121-
// difference does.
122-
let additional: usize = (stop - start).as_();
123-
124-
// TODO(0ax1): optimize this
125-
elements_to_take.reserve_exact(additional);
126-
for i in 0..additional {
127-
elements_to_take.append_value(start + O::from_usize(i).vortex_expect("i < additional"));
128-
}
129-
current_offset +=
130-
OutputOffsetType::from_usize((stop - start).as_()).vortex_expect("offset conversion");
131-
new_offsets.append_value(current_offset);
134+
current_offset = current_offset
135+
.checked_add(length)
136+
.ok_or_else(|| vortex_err!("List take output elements length overflow"))?;
137+
new_offsets.push(new_offset_value::<OutputOffsetType>(current_offset)?);
138+
element_starts.push(start as u64);
139+
element_lengths.push(length as u64);
132140
}
133141

134-
let elements_to_take = elements_to_take.finish();
135-
let new_offsets = new_offsets.finish();
136-
137-
let new_elements = array.elements().take(elements_to_take)?;
142+
let new_offsets = PrimitiveArray::new(new_offsets.freeze(), Validity::NonNullable).into_array();
143+
let multipliers = ConstantArray::new(1u64, element_starts.len()).into_array();
138144

139-
Ok(ListArray::try_new(
140-
new_elements,
141-
new_offsets,
142-
array.validity()?.take(indices_array.array())?,
143-
)?
145+
// SAFETY: valid source rows contribute ranges derived from list offsets; null index/source
146+
// rows contribute zero-length placeholder ranges. `current_offset` is the sum of all generated
147+
// element lengths, and multiplier 1 preserves contiguous ranges.
148+
let element_indices = unsafe {
149+
PiecewiseSequenceArray::new_unchecked(
150+
element_starts.into_array(),
151+
element_lengths.into_array(),
152+
multipliers,
153+
current_offset,
154+
)
155+
};
156+
let new_elements = array.elements().take(element_indices.into_array())?;
157+
158+
// SAFETY: offsets are rebuilt from the gathered element ranges and have one entry per output
159+
// row plus the leading zero; validity is produced by the usual take-validity path.
160+
Ok(unsafe {
161+
ListArray::new_unchecked(
162+
new_elements,
163+
new_offsets,
164+
array.validity()?.take(indices_array.array())?,
165+
)
166+
}
144167
.into_array())
145168
}
146169

@@ -603,83 +626,6 @@ fn new_offset_value<T: IntegerPType>(value: usize) -> VortexResult<T> {
603626
})
604627
}
605628

606-
// Kept out-of-line: as a single-callsite generic helper it would otherwise be inlined into every
607-
// monomorphization of `_take`, duplicating the entire nullable path across all specializations.
608-
#[inline(never)]
609-
fn _take_nullable<I: IntegerPType, O: IntegerPType, OutputOffsetType: IntegerPType>(
610-
array: ArrayView<'_, List>,
611-
offsets_array: ArrayView<'_, Primitive>,
612-
indices_array: ArrayView<'_, Primitive>,
613-
ctx: &mut ExecutionCtx,
614-
) -> VortexResult<ArrayRef> {
615-
let offsets: &[O] = offsets_array.as_slice();
616-
let indices: &[I] = indices_array.as_slice();
617-
let data_validity = array
618-
.list_validity()
619-
.execute_mask(array.as_ref().len(), ctx)?;
620-
let indices_validity = indices_array
621-
.validity()
622-
.vortex_expect("Failed to compute validity mask")
623-
.execute_mask(indices_array.as_ref().len(), ctx)?;
624-
625-
let mut new_offsets = PrimitiveBuilder::<OutputOffsetType>::with_capacity(
626-
Nullability::NonNullable,
627-
indices.len(),
628-
);
629-
630-
// This will be the indices we push down to the child array to call `take` with.
631-
//
632-
// There are 2 things to note here:
633-
// - We do not know how many elements we need to take from our child since lists are variable
634-
// size: thus we arbitrarily choose a capacity of `2 * # of indices`.
635-
// - The type of the primitive builder needs to fit the largest offset of the (parent)
636-
// `ListArray`, so we make this `PrimitiveBuilder` generic over `O` (instead of `I`).
637-
let mut elements_to_take =
638-
PrimitiveBuilder::<O>::with_capacity(Nullability::NonNullable, 2 * indices.len());
639-
640-
let mut current_offset = OutputOffsetType::zero();
641-
new_offsets.append_zero();
642-
643-
for (data_idx, index_valid) in indices.iter().zip(indices_validity.iter()) {
644-
if !index_valid {
645-
new_offsets.append_value(current_offset);
646-
continue;
647-
}
648-
649-
let data_idx: usize = data_idx.as_();
650-
651-
if !data_validity.value(data_idx) {
652-
new_offsets.append_value(current_offset);
653-
continue;
654-
}
655-
656-
let start = offsets[data_idx];
657-
let stop = offsets[data_idx + 1];
658-
659-
// See the note in `_take` on the reasoning.
660-
let additional: usize = (stop - start).as_();
661-
662-
elements_to_take.reserve_exact(additional);
663-
for i in 0..additional {
664-
elements_to_take.append_value(start + O::from_usize(i).vortex_expect("i < additional"));
665-
}
666-
current_offset +=
667-
OutputOffsetType::from_usize((stop - start).as_()).vortex_expect("offset conversion");
668-
new_offsets.append_value(current_offset);
669-
}
670-
671-
let elements_to_take = elements_to_take.finish();
672-
let new_offsets = new_offsets.finish();
673-
let new_elements = array.elements().take(elements_to_take)?;
674-
675-
Ok(ListArray::try_new(
676-
new_elements,
677-
new_offsets,
678-
array.validity()?.take(indices_array.array())?,
679-
)?
680-
.into_array())
681-
}
682-
683629
#[cfg(test)]
684630
mod test {
685631
use std::sync::Arc;

0 commit comments

Comments
 (0)