Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8eeb413
Execute selective primitive Take only for arrays with logical row pre…
danking Oct 26, 2026
faa6e53
Apply pinned upstream Rust formatting
danking Oct 26, 2026
56537d0
Bind the indexed view at the primitive Take entry point
danking Oct 26, 2026
892f41b
Use OnceCell for fallible PCO page-index initialization
danking Oct 27, 2026
199157c
Name the logical-row page index by its purpose
danking Oct 27, 2026
e951879
Remove redundant indexed page view wrapper
danking Oct 27, 2026
855f884
Keep logical page row ranges in u64 until validity access
danking Oct 27, 2026
e18656d
Remove chunk cursor from logical page lookup
danking Oct 27, 2026
24df8ad
Keep chunk reuse in decoder-side page lookup
danking Oct 27, 2026
f7e8c91
Document page decode information lookup
danking Oct 27, 2026
6906efa
Name page-to-chunk lookup explicitly
danking Oct 27, 2026
091a1f1
Describe decoded page value count plainly
danking Oct 27, 2026
7e7fbc0
Preserve existing PCO uninitialized decode buffers
danking Oct 27, 2026
471859e
Separate take bounds validation from contiguous planning
danking Oct 27, 2026
744871b
Branch once for grouped take planning
danking Oct 27, 2026
4fd084a
Use one grouped-plan guard and short-circuit fallback checks
danking Oct 27, 2026
f4c01f9
Limit primitive take to referenced-page selection and gather
danking Oct 27, 2026
dfe1364
Materialize piecewise indices for selective page take
danking Oct 27, 2026
72290f4
Rely on shared empty take handling and one page-index guard
danking Oct 27, 2026
86c2c22
Make validated take extrema an explicit mainline precondition
danking Oct 27, 2026
467b0d3
Use debug assertions for extrema validity invariants
danking Oct 27, 2026
f983f26
Keep rank mask private to page access
danking Oct 27, 2026
1043a2a
Distinguish page indices from prepared pages
danking Oct 27, 2026
795ea78
Scatter PCO take values directly and register the kernel outside regi…
danking Oct 30, 2026
76e27b4
Exercise the registered PCO take kernel in existing test sessions
danking Oct 30, 2026
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions encodings/pco/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ version = { workspace = true }
workspace = true

[dependencies]
once_cell = { workspace = true }
pco = { workspace = true }
prost = { workspace = true }
vortex-array = { workspace = true }
Expand Down
3 changes: 2 additions & 1 deletion encodings/pco/benches/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use vortex_array::session::ArraySessionExt;
use vortex_array::validity::Validity;
use vortex_error::VortexExpect;
use vortex_pco::Pco;
use vortex_pco::initialize as initialize_pco;
use vortex_session::VortexSession;

const ROWS: usize = 4_194_304;
Expand All @@ -53,7 +54,7 @@ static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
let session = VortexSession::empty()
.with::<KernelSession>()
.with::<MemorySession>();
session.arrays().register(Pco);
initialize_pco(&session);
session
});

Expand Down
144 changes: 144 additions & 0 deletions encodings/pco/src/access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

use std::ops::Range;
use std::sync::OnceLock;

use vortex_array::ArrayView;
use vortex_array::ExecutionCtx;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_mask::Mask;

use crate::Pco;
use crate::PcoArrayExt;
use crate::page_index::LogicalPageIndex;

const RANK_STRIDE: usize = 512;
struct RankMask {
mask: Mask,
ranks: OnceLock<Vec<usize>>,
}

impl RankMask {
fn new(mask: Mask) -> Self {
Self {
mask,
ranks: OnceLock::new(),
}
}

fn boundary_rank(&self, row: usize) -> usize {
match &self.mask {
Mask::AllTrue(_) => row,
Mask::AllFalse(_) => 0,
Mask::Values(values) => {
let bits = values.bit_buffer();
if row <= bits.len() - row {
bits.count_range(0, row)
} else {
self.mask.true_count() - bits.count_range(row, bits.len())
}
}
}
}

fn rank(&self, row: usize) -> usize {
match &self.mask {
Mask::AllTrue(_) => row,
Mask::AllFalse(_) => 0,
Mask::Values(values) => {
if row == self.mask.len() {
return self.mask.true_count();
}
let ranks = self.ranks.get_or_init(|| {
let bits = values.bit_buffer();
let mut ranks = Vec::with_capacity(bits.len().div_ceil(RANK_STRIDE));
let mut count = 0;
for start in (0..bits.len()).step_by(RANK_STRIDE) {
ranks.push(count);
count += bits.count_range(start, (start + RANK_STRIDE).min(bits.len()));
}
ranks
});
let block = row / RANK_STRIDE;
ranks[block] + values.bit_buffer().count_range(block * RANK_STRIDE, row)
}
}
}

fn value_index<const INDEXED: bool>(&self, row: usize) -> Option<usize> {
match &self.mask {
Mask::AllTrue(_) => Some(row),
Mask::AllFalse(_) => None,
Mask::Values(values) => {
let bits = values.bit_buffer();
bits.value(row).then(|| {
if INDEXED {
self.rank(row)
} else {
self.boundary_rank(row)
}
})
}
}
}
}

/// Only new-prefix arrays use this operation-owned last-page validity preparation.
#[derive(Default)]
pub(crate) struct AccessState {
last: Option<(usize, PreparedPage)>,
}
struct PreparedPage {
rows: Range<usize>,
mask: RankMask,
}
impl AccessState {
fn page(
&mut self,
view: &LogicalPageIndex,
array: ArrayView<'_, Pco>,
row: usize,
ctx: &mut ExecutionCtx,
) -> VortexResult<(usize, &PreparedPage)> {
if !self
.last
.as_ref()
.is_some_and(|(_, page)| page.rows.contains(&row))
{
let page_index = view.find_row(row);
let rows = view.rows(page_index);
let rows = usize::try_from(rows.start)
.vortex_expect("page row start must fit the validity array")
..usize::try_from(rows.end)
.vortex_expect("page row end must fit the validity array");
let mask = array
.unsliced_validity()
.slice(rows.clone())?
.execute_mask(rows.len(), ctx)?;
self.last = Some((
page_index,
PreparedPage {
rows,
mask: RankMask::new(mask),
},
));
}
let (page_index, page) = self.last.as_ref().vortex_expect("page prepared above");
Ok((*page_index, page))
}
pub fn resolve<const RANKED: bool>(
&mut self,
view: &LogicalPageIndex,
array: ArrayView<'_, Pco>,
row: usize,
ctx: &mut ExecutionCtx,
) -> VortexResult<Option<(usize, usize)>> {
let (page_index, page) = self.page(view, array, row, ctx)?;
Ok(page
.mask
.value_index::<RANKED>(row - page.rows.start)
.map(|value| (page_index, value)))
}
}
10 changes: 10 additions & 0 deletions encodings/pco/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;

use once_cell::sync::OnceCell;
use pco::ChunkConfig;
use pco::PagingSpec;
use pco::data_types::Number;
Expand Down Expand Up @@ -50,6 +51,7 @@ use vortex_array::vtable::validity_to_child;
use vortex_buffer::BufferMut;
use vortex_buffer::ByteBuffer;
use vortex_error::VortexError;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
Expand All @@ -63,6 +65,7 @@ use crate::PcoChunkInfo;
use crate::PcoLogicalRowPrefix;
use crate::PcoMetadata;
use crate::PcoPageInfo;
use crate::page_index::PageIndex;

// Overall approach here:
// Chunk the array into Pco chunks (currently using the default recommended size
Expand Down Expand Up @@ -406,6 +409,7 @@ pub struct PcoData {
pub(crate) chunk_metas: Arc<Vec<ByteBuffer>>,
pub(crate) pages: Arc<Vec<ByteBuffer>>,
pub(crate) metadata: Arc<PcoMetadata>,
page_index: Arc<OnceCell<PageIndex>>,
ptype: PType,
unsliced_n_rows: usize,
slice_start: usize,
Expand Down Expand Up @@ -505,6 +509,7 @@ impl PcoData {
chunk_metas: chunk_metas.into(),
pages: pages.into(),
metadata: Arc::new(metadata),
page_index: Arc::new(OnceCell::new()),
ptype,
unsliced_n_rows: len,
slice_start: 0,
Expand Down Expand Up @@ -781,6 +786,11 @@ impl PcoData {
.into_byte_buffer())
}

pub(crate) fn page_index(&self) -> VortexResult<&PageIndex> {
self.page_index
.get_or_try_init(|| PageIndex::try_new(Arc::clone(&self.metadata)))
}

pub(crate) fn _slice(&self, start: usize, stop: usize) -> Self {
PcoData {
slice_start: self.slice_start + start,
Expand Down
100 changes: 100 additions & 0 deletions encodings/pco/src/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

//! File and chunk decoding setup amortized over all selected pages in an operation.

use std::collections::BTreeMap;
use std::collections::btree_map::Entry;

use pco::data_types::Number;
use pco::wrapped::ChunkDecompressor;
use pco::wrapped::FileDecompressor;
use vortex_array::ArrayView;
use vortex_array::ExecutionCtx;
use vortex_array::arrays::PrimitiveArray;
use vortex_array::dtype::NativePType;
use vortex_array::validity::Validity;
use vortex_buffer::BufferMut;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_err;

use crate::Pco;
use crate::array::vortex_err_from_pco;
use crate::page_index::LogicalPageIndex;
use crate::page_index::PhysicalPage;

pub(crate) struct PageDecoder<T: Number> {
file: FileDecompressor,
chunks: BTreeMap<usize, ChunkDecompressor<T>>,
}

impl<T: Number + NativePType> PageDecoder<T> {
pub fn new(header: &[u8]) -> VortexResult<Self> {
let (file, _) = FileDecompressor::new(header).map_err(vortex_err_from_pco)?;
Ok(Self {
file,
chunks: BTreeMap::new(),
})
}

pub fn read(
&mut self,
chunk: usize,
chunk_meta: &[u8],
n_values: usize,
buffer: &[u8],
ctx: &mut ExecutionCtx,
) -> VortexResult<PrimitiveArray> {
let mut values = BufferMut::<T>::with_capacity_in(n_values, ctx.allocator().clone());
unsafe {
values.set_len(n_values);
}
let chunk = match self.chunks.entry(chunk) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let (decoder, _) = self
.file
.chunk_decompressor::<T, _>(chunk_meta)
.map_err(vortex_err_from_pco)?;
entry.insert(decoder)
}
};
let mut decoder = chunk
.page_decompressor(buffer, n_values)
.map_err(vortex_err_from_pco)?;
decoder.read(&mut values).map_err(vortex_err_from_pco)?;
Ok(PrimitiveArray::new(values.freeze(), Validity::NonNullable))
}
}

#[derive(Default)]
pub(crate) struct PageLookup {
last: Option<PhysicalPage>,
}

impl PageLookup {
pub fn resolve(
&mut self,
index: &LogicalPageIndex,
array: ArrayView<'_, Pco>,
page: usize,
) -> VortexResult<(usize, usize)> {
if !self
.last
.as_ref()
.is_some_and(|last| last.pages.contains(&page))
{
let info = index.chunk_for_page(page)?;
let result = (info.chunk, info.n_values);
self.last = Some(info);
return Ok(result);
}
let last = self.last.as_ref().vortex_expect("chunk lookup initialized");
let info = array.metadata.chunks[last.chunk]
.pages
.get(page - last.pages.start)
.ok_or_else(|| vortex_err!("Pco page metadata is missing"))?;
Ok((last.chunk, info.n_values as usize))
}
}
28 changes: 24 additions & 4 deletions encodings/pco/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,33 @@
//!
//! Pco supports integer and floating-point primitive dtypes handled by the upstream `pco` crate.
//! It is normally selected through the BtrBlocks compressor when the `pco` feature is enabled.
//! To deserialize arrays manually, register the encoding in the array session:
//! To register the encoding and its selective take kernel in a session:
//!
//! ```rust
//! use vortex_array::session::ArraySessionExt;
//! use vortex_pco::initialize;
//! use vortex_session::VortexSession;
//! use vortex_pco::Pco;
//!
//! let session = VortexSession::empty();
//! session.arrays().register(Pco);
//! initialize(&session);
//! ```

use prost::Message;
use vortex_array::ArrayVTable;
use vortex_array::arrays::Dict;
use vortex_array::arrays::dict::TakeExecuteAdaptor;
use vortex_array::optimizer::kernels::ArrayKernelsExt;
use vortex_array::session::ArraySessionExt;
use vortex_session::VortexSession;

mod access;
mod array;
mod compute;
mod decoder;
mod page_index;
mod probe;
mod rules;
mod slice;
mod take;

pub use array::*;

Expand Down Expand Up @@ -77,6 +86,17 @@ pub struct PcoLogicalRowPrefix {
pub logical_row_ends: Vec<u64>,
}

/// Register PCO encoding and its selective take execution kernel in a session.
///
/// Registering only `Pco` in the array registry is sufficient for legacy reads, but does not
/// enable the batched take kernel.
pub fn initialize(session: &VortexSession) {
session.arrays().register(Pco);
session
.kernels()
.register_execute_parent_kernel(Dict.id(), Pco, TakeExecuteAdaptor(Pco));
}

#[cfg(test)]
mod tests;

Expand Down
Loading