Skip to content
Open
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
22 changes: 22 additions & 0 deletions docs/developer-guide/internals/execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,28 @@ Rules of thumb:
- Treat `execute` as the fallback. If no reduce rule or parent kernel applies, the encoding
decodes itself and uses `ExecuteChild` to request child execution when needed.

## In-Place Mutation via Owned Arrays and Buffers

Vortex execution passes arrays by owned `ArrayRef` (`Arc<dyn DynArray>`) wherever possible.
When the `Arc` reference count is 1, the executor can mutate the array and its buffers in place,
avoiding unnecessary allocations and copies. This is a key performance optimization that threads
through two levels of the stack: **arrays** and **buffers**.

At the array level, `take_slot` and `put_slot` use `Arc::get_mut` to check for unique
ownership. When the refcount is 1, the slot is modified in place without cloning the parent.
At the buffer level, `Buffer::try_into_mut` converts an immutable `Buffer<T>` into a
`BufferMut<T>` without copying when the underlying allocation has a single reference. Kernels
use this to write results directly into their input buffers.

The execution loop is structured so that this works naturally. When the scheduler pushes a
parent onto the work stack via `ExecuteSlot`, it takes the child out with `take_slot` and
moves the parent onto the stack — giving it refcount 1. The child is then executed
independently. Crucially, leaf arrays (the ones that actually touch buffers) are produced
fresh by decode steps — they are new allocations with no other references. As results propagate
back up the stack, each `put_slot` finds its parent with refcount 1 (it was owned on the
stack), and each buffer produced by a leaf kernel has refcount 1 (it was just created). So
the in-place mutation path is hit at every level by construction, not by accident.

## Future Work

The execution model is designed to support additional function types beyond scalar functions:
Expand Down
8 changes: 4 additions & 4 deletions encodings/alp/public-api.lock
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ pub fn vortex_alp::ALP::slot_name(_array: &vortex_alp::ALPArray, idx: usize) ->

pub fn vortex_alp::ALP::slots(array: &vortex_alp::ALPArray) -> &[core::option::Option<vortex_array::array::ArrayRef>]

pub fn vortex_alp::ALP::slots_mut(array: &mut vortex_alp::ALPArray) -> &mut [core::option::Option<vortex_array::array::ArrayRef>]

pub fn vortex_alp::ALP::stats(array: &vortex_alp::ALPArray) -> vortex_array::stats::array::StatsSetRef<'_>

pub fn vortex_alp::ALP::vtable(_array: &Self::Array) -> &Self

pub fn vortex_alp::ALP::with_slots(array: &mut vortex_alp::ALPArray, slots: alloc::vec::Vec<core::option::Option<vortex_array::array::ArrayRef>>) -> vortex_error::VortexResult<()>

impl vortex_array::vtable::operations::OperationsVTable<vortex_alp::ALP> for vortex_alp::ALP

pub fn vortex_alp::ALP::scalar_at(array: &vortex_alp::ALPArray, index: usize, _ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult<vortex_array::scalar::Scalar>
Expand Down Expand Up @@ -252,12 +252,12 @@ pub fn vortex_alp::ALPRD::slot_name(_array: &vortex_alp::ALPRDArray, idx: usize)

pub fn vortex_alp::ALPRD::slots(array: &vortex_alp::ALPRDArray) -> &[core::option::Option<vortex_array::array::ArrayRef>]

pub fn vortex_alp::ALPRD::slots_mut(array: &mut vortex_alp::ALPRDArray) -> &mut [core::option::Option<vortex_array::array::ArrayRef>]

pub fn vortex_alp::ALPRD::stats(array: &vortex_alp::ALPRDArray) -> vortex_array::stats::array::StatsSetRef<'_>

pub fn vortex_alp::ALPRD::vtable(_array: &Self::Array) -> &Self

pub fn vortex_alp::ALPRD::with_slots(array: &mut vortex_alp::ALPRDArray, slots: alloc::vec::Vec<core::option::Option<vortex_array::array::ArrayRef>>) -> vortex_error::VortexResult<()>

impl vortex_array::vtable::operations::OperationsVTable<vortex_alp::ALPRD> for vortex_alp::ALPRD

pub fn vortex_alp::ALPRD::scalar_at(array: &vortex_alp::ALPRDArray, index: usize, _ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult<vortex_array::scalar::Scalar>
Expand Down
18 changes: 2 additions & 16 deletions encodings/alp/src/alp/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,8 @@ impl VTable for ALP {
SLOT_NAMES[idx].to_string()
}

fn with_slots(array: &mut ALPArray, slots: Vec<Option<ArrayRef>>) -> VortexResult<()> {
vortex_ensure!(
slots.len() == NUM_SLOTS,
"ALPArray expects {} slots, got {}",
NUM_SLOTS,
slots.len()
);

// If patch slots are being cleared, clear the metadata too
if slots[PATCH_INDICES_SLOT].is_none() || slots[PATCH_VALUES_SLOT].is_none() {
array.patch_offset = None;
array.patch_offset_within_chunk = None;
}

array.slots = slots;
Ok(())
fn slots_mut(array: &mut ALPArray) -> &mut [Option<ArrayRef>] {
&mut array.slots
}

fn metadata(array: &ALPArray) -> VortexResult<Self::Metadata> {
Expand Down
89 changes: 48 additions & 41 deletions encodings/alp/src/alp_rd/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ use vortex_buffer::Buffer;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
use vortex_error::vortex_err;
use vortex_error::vortex_panic;
use vortex_session::VortexSession;
Expand Down Expand Up @@ -99,7 +98,7 @@ impl VTable for ALPRD {
array.left_parts_dictionary.array_hash(state, precision);
array.right_parts().array_hash(state, precision);
array.right_bit_width.hash(state);
array.left_parts_patches.array_hash(state, precision);
array.left_parts_patches().array_hash(state, precision);
}

fn array_eq(array: &ALPRDArray, other: &ALPRDArray, precision: Precision) -> bool {
Expand All @@ -111,8 +110,8 @@ impl VTable for ALPRD {
&& array.right_parts().array_eq(other.right_parts(), precision)
&& array.right_bit_width == other.right_bit_width
&& array
.left_parts_patches
.array_eq(&other.left_parts_patches, precision)
.left_parts_patches()
.array_eq(&other.left_parts_patches(), precision)
}

fn nbuffers(_array: &ALPRDArray) -> usize {
Expand Down Expand Up @@ -239,34 +238,8 @@ impl VTable for ALPRD {
SLOT_NAMES[idx].to_string()
}

fn with_slots(array: &mut ALPRDArray, slots: Vec<Option<ArrayRef>>) -> VortexResult<()> {
vortex_ensure!(
slots.len() == NUM_SLOTS,
"ALPRDArray expects {} slots, got {}",
NUM_SLOTS,
slots.len()
);

// Reconstruct patches from slots + existing metadata
array.left_parts_patches =
match (&slots[LP_PATCH_INDICES_SLOT], &slots[LP_PATCH_VALUES_SLOT]) {
(Some(indices), Some(values)) => {
let old = array
.left_parts_patches
.as_ref()
.vortex_expect("ALPRDArray had patch slots but no patches metadata");
Some(Patches::new(
old.array_len(),
old.offset(),
indices.clone(),
values.clone(),
slots[LP_PATCH_CHUNK_OFFSETS_SLOT].clone(),
)?)
}
_ => None,
};
array.slots = slots;
Ok(())
fn slots_mut(array: &mut ALPRDArray) -> &mut [Option<ArrayRef>] {
&mut array.slots
}

fn execute(array: Arc<Array<Self>>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
Expand Down Expand Up @@ -374,7 +347,8 @@ pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = [
pub struct ALPRDArray {
dtype: DType,
slots: Vec<Option<ArrayRef>>,
left_parts_patches: Option<Patches>,
patch_offset: Option<usize>,
patch_offset_within_chunk: Option<usize>,
left_parts_dictionary: Buffer<u16>,
right_bit_width: u8,
stats_set: ArrayStats,
Expand Down Expand Up @@ -452,13 +426,18 @@ impl ALPRDArray {
.transpose()?;

let slots = Self::make_slots(&left_parts, &right_parts, &left_parts_patches);
let (patch_offset, patch_offset_within_chunk) = match &left_parts_patches {
Some(p) => (Some(p.offset()), p.offset_within_chunk()),
None => (None, None),
};

Ok(Self {
dtype,
slots,
patch_offset,
patch_offset_within_chunk,
left_parts_dictionary,
right_bit_width,
left_parts_patches,
stats_set: Default::default(),
})
}
Expand All @@ -474,11 +453,16 @@ impl ALPRDArray {
left_parts_patches: Option<Patches>,
) -> Self {
let slots = Self::make_slots(&left_parts, &right_parts, &left_parts_patches);
let (patch_offset, patch_offset_within_chunk) = match &left_parts_patches {
Some(p) => (Some(p.offset()), p.offset_within_chunk()),
None => (None, None),
};

Self {
dtype,
slots,
left_parts_patches,
patch_offset,
patch_offset_within_chunk,
left_parts_dictionary,
right_bit_width,
stats_set: Default::default(),
Expand Down Expand Up @@ -509,6 +493,7 @@ impl ALPRDArray {

/// Return all the owned parts of the array
pub fn into_parts(mut self) -> ALPRDArrayParts {
let left_parts_patches = self.left_parts_patches();
let left_parts = self.slots[LEFT_PARTS_SLOT]
.take()
.vortex_expect("ALPRDArray left_parts slot");
Expand All @@ -518,7 +503,7 @@ impl ALPRDArray {
ALPRDArrayParts {
dtype: self.dtype,
left_parts,
left_parts_patches: self.left_parts_patches,
left_parts_patches,
left_parts_dictionary: self.left_parts_dictionary,
right_parts,
}
Expand Down Expand Up @@ -556,7 +541,27 @@ impl ALPRDArray {

/// Patches of left-most bits.
pub fn left_parts_patches(&self) -> Option<Patches> {
self.left_parts_patches.clone()
match (
&self.slots[LP_PATCH_INDICES_SLOT],
&self.slots[LP_PATCH_VALUES_SLOT],
) {
(Some(indices), Some(values)) => {
let patch_offset = self
.patch_offset
.vortex_expect("has patch slots but no patch_offset");
Some(unsafe {
Patches::new_unchecked(
self.left_parts().len(),
patch_offset,
indices.clone(),
values.clone(),
self.slots[LP_PATCH_CHUNK_OFFSETS_SLOT].clone(),
self.patch_offset_within_chunk,
)
})
}
_ => None,
}
}

/// The dictionary that maps the codes in `left_parts` into bit patterns.
Expand All @@ -566,19 +571,21 @@ impl ALPRDArray {
}

pub fn replace_left_parts_patches(&mut self, patches: Option<Patches>) {
// Update both the patches and the corresponding slots to keep them in sync.
let (pi, pv, pco) = match &patches {
let (pi, pv, pco, patch_offset, patch_offset_within_chunk) = match &patches {
Some(p) => (
Some(p.indices().clone()),
Some(p.values().clone()),
p.chunk_offsets().clone(),
Some(p.offset()),
p.offset_within_chunk(),
),
None => (None, None, None),
None => (None, None, None, None, None),
};
self.slots[LP_PATCH_INDICES_SLOT] = pi;
self.slots[LP_PATCH_VALUES_SLOT] = pv;
self.slots[LP_PATCH_CHUNK_OFFSETS_SLOT] = pco;
self.left_parts_patches = patches;
self.patch_offset = patch_offset;
self.patch_offset_within_chunk = patch_offset_within_chunk;
}
}

Expand Down
14 changes: 7 additions & 7 deletions encodings/bytebool/public-api.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub type vortex_bytebool::ByteBool::Metadata = vortex_array::metadata::EmptyMeta

pub type vortex_bytebool::ByteBool::OperationsVTable = vortex_bytebool::ByteBool

pub type vortex_bytebool::ByteBool::ValidityVTable = vortex_array::vtable::validity::ValidityVTableFromValidityHelper
pub type vortex_bytebool::ByteBool::ValidityVTable = vortex_bytebool::ByteBool

pub fn vortex_bytebool::ByteBool::array_eq(array: &vortex_bytebool::ByteBoolArray, other: &vortex_bytebool::ByteBoolArray, precision: vortex_array::hash::Precision) -> bool

Expand Down Expand Up @@ -74,16 +74,20 @@ pub fn vortex_bytebool::ByteBool::slot_name(_array: &vortex_bytebool::ByteBoolAr

pub fn vortex_bytebool::ByteBool::slots(array: &vortex_bytebool::ByteBoolArray) -> &[core::option::Option<vortex_array::array::ArrayRef>]

pub fn vortex_bytebool::ByteBool::slots_mut(array: &mut vortex_bytebool::ByteBoolArray) -> &mut [core::option::Option<vortex_array::array::ArrayRef>]

pub fn vortex_bytebool::ByteBool::stats(array: &vortex_bytebool::ByteBoolArray) -> vortex_array::stats::array::StatsSetRef<'_>

pub fn vortex_bytebool::ByteBool::vtable(_array: &Self::Array) -> &Self

pub fn vortex_bytebool::ByteBool::with_slots(array: &mut vortex_bytebool::ByteBoolArray, slots: alloc::vec::Vec<core::option::Option<vortex_array::array::ArrayRef>>) -> vortex_error::VortexResult<()>

impl vortex_array::vtable::operations::OperationsVTable<vortex_bytebool::ByteBool> for vortex_bytebool::ByteBool

pub fn vortex_bytebool::ByteBool::scalar_at(array: &vortex_bytebool::ByteBoolArray, index: usize, _ctx: &mut vortex_array::executor::ExecutionCtx) -> vortex_error::VortexResult<vortex_array::scalar::Scalar>

impl vortex_array::vtable::validity::ValidityVTable<vortex_bytebool::ByteBool> for vortex_bytebool::ByteBool

pub fn vortex_bytebool::ByteBool::validity(array: &vortex_bytebool::ByteBoolArray) -> vortex_error::VortexResult<vortex_array::validity::Validity>

pub struct vortex_bytebool::ByteBoolArray

impl vortex_bytebool::ByteBoolArray
Expand Down Expand Up @@ -133,7 +137,3 @@ pub fn vortex_bytebool::ByteBoolArray::deref(&self) -> &Self::Target
impl vortex_array::array::IntoArray for vortex_bytebool::ByteBoolArray

pub fn vortex_bytebool::ByteBoolArray::into_array(self) -> vortex_array::array::ArrayRef

impl vortex_array::vtable::validity::ValidityHelper for vortex_bytebool::ByteBoolArray

pub fn vortex_bytebool::ByteBoolArray::validity(&self) -> vortex_array::validity::Validity
Loading
Loading