From 09c668eeb9dc7b27ba5aae0d3711394508d70524 Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 13:50:25 -0400 Subject: [PATCH 01/10] Benchmark public PCO selection and explicitly batched probe workloads Signed-off-by: Daniel King --- encodings/pco/Cargo.toml | 4 + encodings/pco/benches/selection.rs | 175 +++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 encodings/pco/benches/selection.rs diff --git a/encodings/pco/Cargo.toml b/encodings/pco/Cargo.toml index 2262591e837..e2151e2a958 100644 --- a/encodings/pco/Cargo.toml +++ b/encodings/pco/Cargo.toml @@ -34,3 +34,7 @@ vortex-arrow = { workspace = true } [[bench]] name = "scalar" harness = false + +[[bench]] +name = "selection" +harness = false diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs new file mode 100644 index 00000000000..be16b812983 --- /dev/null +++ b/encodings/pco/benches/selection.rs @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +//! Public PCO operations with fixed workloads shared by every implementation. +//! Compression/index fixtures and warm-cache preparation are outside the measured operation. +//! Small scalar requests are explicit batches; divide by PROBES for time per probe. + +use std::hint::black_box; +use std::sync::LazyLock; + +use divan::Bencher; +use vortex_array::ArrayRef; +use vortex_array::IntoArray; +use vortex_array::VortexSessionExecute; +use vortex_array::arrays::PiecewiseSequenceArray; +use vortex_array::arrays::PrimitiveArray; +use vortex_array::memory::MemorySession; +use vortex_array::optimizer::kernels::KernelSession; +use vortex_array::session::ArraySessionExt; +use vortex_array::validity::Validity; +use vortex_error::VortexExpect; +use vortex_pco::Pco; +use vortex_session::VortexSession; + +const ROWS: usize = 4_194_304; +const PROBES: usize = 1_048_576; +const FIRST_PROBES: usize = 256; +static SESSION: LazyLock = LazyLock::new(|| { + let session = VortexSession::empty() + .with::() + .with::(); + session.arrays().register(Pco); + session +}); + +fn main() { + divan::main(); +} + +fn input(nullable: bool) -> PrimitiveArray { + PrimitiveArray::new( + (0..ROWS) + .map(|i| (i as i64).wrapping_mul(17) ^ ((i / 1024) as i64)) + .collect::>(), + if nullable { + Validity::from_iter((0..ROWS).map(|i| i % 7 != 0)) + } else { + Validity::NonNullable + }, + ) +} + +fn compressed(nullable: bool) -> ArrayRef { + let mut ctx = SESSION.create_execution_ctx(); + Pco::from_primitive(input(nullable).as_view(), 3, 1024, &mut ctx) + .vortex_expect("valid benchmark fixture") + .into_array() +} + +#[divan::bench(args = [false, true])] +fn write(bencher: Bencher, nullable: bool) { + let input = input(nullable); + bencher + .with_inputs(|| SESSION.create_execution_ctx()) + .bench_refs(|ctx| { + Pco::from_primitive(input.as_view(), 3, 1024, ctx).vortex_expect("compression") + }); +} + +#[divan::bench(args = [false, true])] +fn full(bencher: Bencher, nullable: bool) { + let source = compressed(nullable); + bencher + .with_inputs(|| SESSION.create_execution_ctx()) + .bench_refs(|ctx| { + source + .execute::(ctx) + .vortex_expect("full decode") + }); +} + +const SELECTIONS: &[(&str, bool)] = &[ + ("sparse", false), + ("sparse", true), + ("broad", false), + ("broad", true), + ("repeated", false), + ("repeated", true), + ("near_full", false), + ("near_full", true), + ("ranges", false), + ("ranges", true), +]; + +#[divan::bench(args = SELECTIONS)] +fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { + let source = compressed(nullable); + let indices = match kind { + "sparse" => { + PrimitiveArray::from_iter((0..128u64).map(|i| (i * 32749) % ROWS as u64)).into_array() + } + "broad" => PrimitiveArray::from_iter((0..524288u64).map(|i| (i * 32749) % ROWS as u64)) + .into_array(), + "repeated" => { + PrimitiveArray::from_iter((0..524288u64).map(|i| (i % 16) * 127)).into_array() + } + "ranges" => PiecewiseSequenceArray::try_new( + PrimitiveArray::from_iter([0u64, 100000, 200000, 300000]).into_array(), + PrimitiveArray::from_iter([512u64; 4]).into_array(), + PrimitiveArray::from_iter([1u64; 4]).into_array(), + 2048, + ) + .vortex_expect("range indices") + .into_array(), + _ => PrimitiveArray::from_iter([0u64]).into_array(), + }; + bencher + .with_inputs(|| SESSION.create_execution_ctx()) + .bench_refs(|ctx| { + let result = if kind == "near_full" { + source.slice(1..ROWS - 1).vortex_expect("slice") + } else { + source.take(indices.clone()).vortex_expect("take") + }; + result + .execute::(ctx) + .vortex_expect("selection") + }); +} + +#[divan::bench(args = [false, true])] +fn retained_probes(bencher: Bencher, nullable: bool) { + let source = compressed(nullable); + bencher + .with_inputs(|| { + let mut ctx = SESSION.create_execution_ctx(); + let mut probe = source.repeated_probe(); + for i in 0..256 { + black_box( + probe + .execute_scalar(8192 + i, &mut ctx) + .vortex_expect("warm page"), + ); + } + (ctx, probe) + }) + .bench_refs(|(ctx, probe)| { + for i in 0..PROBES { + black_box( + probe + .execute_scalar(8192 + i % 256, ctx) + .vortex_expect("retained probe"), + ); + } + }); +} + +// Each request starts a new probe. Missing and defined rows are separate cases. +#[divan::bench(args = [false, true])] +fn first_probes(bencher: Bencher, missing: bool) { + let source = compressed(true); + let index = if missing { 8190 } else { 8191 }; + bencher + .with_inputs(|| SESSION.create_execution_ctx()) + .bench_refs(|ctx| { + for _ in 0..FIRST_PROBES { + black_box( + source + .probe() + .execute_scalar(index, ctx) + .vortex_expect("first probe"), + ); + } + }); +} From 1ac85fafdbac631fe37b61d7e2c97f120558b310 Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 13:53:45 -0400 Subject: [PATCH 02/10] Check every benchmark result against public primitive oracles outside timing Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 53 +++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index be16b812983..7d540e88256 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -3,7 +3,8 @@ //! Public PCO operations with fixed workloads shared by every implementation. //! Compression/index fixtures and warm-cache preparation are outside the measured operation. -//! Small scalar requests are explicit batches; divide by PROBES for time per probe. +//! Retained batches have PROBES requests; fresh-state batches have FIRST_PROBES requests. +//! Divide by the corresponding count for per-probe time. Fresh state excludes owner/index opening. use std::hint::black_box; use std::sync::LazyLock; @@ -14,6 +15,7 @@ use vortex_array::IntoArray; use vortex_array::VortexSessionExecute; use vortex_array::arrays::PiecewiseSequenceArray; use vortex_array::arrays::PrimitiveArray; +use vortex_array::assert_arrays_eq; use vortex_array::memory::MemorySession; use vortex_array::optimizer::kernels::KernelSession; use vortex_array::session::ArraySessionExt; @@ -60,6 +62,10 @@ fn compressed(nullable: bool) -> ArrayRef { #[divan::bench(args = [false, true])] fn write(bencher: Bencher, nullable: bool) { let input = input(nullable); + let mut check = SESSION.create_execution_ctx(); + let written = + Pco::from_primitive(input.as_view(), 3, 1024, &mut check).vortex_expect("write oracle"); + assert_arrays_eq!(written.into_array(), input.clone(), &mut check); bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { @@ -70,6 +76,8 @@ fn write(bencher: Bencher, nullable: bool) { #[divan::bench(args = [false, true])] fn full(bencher: Bencher, nullable: bool) { let source = compressed(nullable); + let mut check = SESSION.create_execution_ctx(); + assert_arrays_eq!(source.clone(), input(nullable), &mut check); bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { @@ -114,6 +122,21 @@ fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { .into_array(), _ => PrimitiveArray::from_iter([0u64]).into_array(), }; + let mut check = SESSION.create_execution_ctx(); + let reference = input(nullable).into_array(); + let actual = if kind == "near_full" { + source.slice(1..ROWS - 1) + } else { + source.take(indices.clone()) + } + .vortex_expect("selection oracle"); + let expected = if kind == "near_full" { + reference.slice(1..ROWS - 1) + } else { + reference.take(indices.clone()) + } + .vortex_expect("primitive oracle"); + assert_arrays_eq!(actual, expected, &mut check); bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { @@ -131,6 +154,20 @@ fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { #[divan::bench(args = [false, true])] fn retained_probes(bencher: Bencher, nullable: bool) { let source = compressed(nullable); + let reference = input(nullable).into_array(); + let mut check = SESSION.create_execution_ctx(); + let mut checked_probe = source.repeated_probe(); + for i in 0..256 { + assert_eq!( + checked_probe + .execute_scalar(8192 + i, &mut check) + .vortex_expect("probe oracle"), + reference + .execute_scalar(8192 + i, &mut check) + .vortex_expect("primitive probe") + ); + } + bencher .with_inputs(|| { let mut ctx = SESSION.create_execution_ctx(); @@ -160,6 +197,20 @@ fn retained_probes(bencher: Bencher, nullable: bool) { fn first_probes(bencher: Bencher, missing: bool) { let source = compressed(true); let index = if missing { 8190 } else { 8191 }; + let mut check = SESSION.create_execution_ctx(); + let actual = source + .probe() + .execute_scalar(index, &mut check) + .vortex_expect("fresh probe oracle"); + assert_eq!( + actual, + input(true) + .into_array() + .execute_scalar(index, &mut check) + .vortex_expect("primitive oracle") + ); + assert_eq!(actual.is_null(), missing); + bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { From 2ffabd6410973d4cd30fcb7fdc4abc558dfc01ad Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 13:58:02 -0400 Subject: [PATCH 03/10] Release untimed oracle state before measuring PCO workloads Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index 7d540e88256..e47450142d5 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -66,6 +66,7 @@ fn write(bencher: Bencher, nullable: bool) { let written = Pco::from_primitive(input.as_view(), 3, 1024, &mut check).vortex_expect("write oracle"); assert_arrays_eq!(written.into_array(), input.clone(), &mut check); + drop(check); bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { @@ -78,6 +79,7 @@ fn full(bencher: Bencher, nullable: bool) { let source = compressed(nullable); let mut check = SESSION.create_execution_ctx(); assert_arrays_eq!(source.clone(), input(nullable), &mut check); + drop(check); bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { @@ -137,6 +139,8 @@ fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { } .vortex_expect("primitive oracle"); assert_arrays_eq!(actual, expected, &mut check); + drop(reference); + drop(check); bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { @@ -168,6 +172,9 @@ fn retained_probes(bencher: Bencher, nullable: bool) { ); } + drop(checked_probe); + drop(reference); + drop(check); bencher .with_inputs(|| { let mut ctx = SESSION.create_execution_ctx(); @@ -210,7 +217,9 @@ fn first_probes(bencher: Bencher, missing: bool) { .vortex_expect("primitive oracle") ); assert_eq!(actual.is_null(), missing); + drop(actual); + drop(check); bencher .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { From d6b5c09d92a27f741c0d7467c87c0c3a49ab71d0 Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 14:01:15 -0400 Subject: [PATCH 04/10] Clone the public full-decode source for each benchmark invocation Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index e47450142d5..d2866a48eda 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -84,6 +84,7 @@ fn full(bencher: Bencher, nullable: bool) { .with_inputs(|| SESSION.create_execution_ctx()) .bench_refs(|ctx| { source + .clone() .execute::(ctx) .vortex_expect("full decode") }); From c3f13679e90a81addffc372c55c39f03e02a9feb Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 14:03:25 -0400 Subject: [PATCH 05/10] Benchmark public PCO metadata preparation separately from selection Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index d2866a48eda..c77ee467fe5 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -10,12 +10,15 @@ use std::hint::black_box; use std::sync::LazyLock; use divan::Bencher; +use vortex_array::ArrayDeserialization; +use vortex_array::ArrayPlugin; use vortex_array::ArrayRef; use vortex_array::IntoArray; use vortex_array::VortexSessionExecute; use vortex_array::arrays::PiecewiseSequenceArray; use vortex_array::arrays::PrimitiveArray; use vortex_array::assert_arrays_eq; +use vortex_array::buffer::BufferHandle; use vortex_array::memory::MemorySession; use vortex_array::optimizer::kernels::KernelSession; use vortex_array::session::ArraySessionExt; @@ -234,3 +237,42 @@ fn first_probes(bencher: Bencher, missing: bool) { } }); } + +// Buffers/children are already materialized: this measures metadata/index preparation, not IO. +#[divan::bench(args = [false, true])] +fn metadata_open_batch(bencher: Bencher, nullable: bool) { + const OPENS: usize = 256; + let source = compressed(nullable); + let wire = SESSION + .array_serialize(&source) + .vortex_expect("serialize metadata fixture") + .vortex_expect("PCO is serializable"); + let buffers = wire + .buffers + .into_iter() + .map(BufferHandle::new_host) + .collect::>(); + let open = || { + ArrayPlugin::deserialize( + &Pco, + ArrayDeserialization::new( + wire.serialized_id, + source.dtype(), + source.len(), + &wire.metadata, + &buffers, + &wire.children, + ), + &SESSION, + ) + .vortex_expect("public metadata open") + }; + let mut check = SESSION.create_execution_ctx(); + assert_arrays_eq!(open(), input(nullable), &mut check); + drop(check); + bencher.bench_local(|| { + for _ in 0..OPENS { + black_box(open()); + } + }); +} From 0c6cf14e463dc52fcf0bf06407f07be3e2bc44d5 Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 14:09:35 -0400 Subject: [PATCH 06/10] Avoid a redundant oracle clone outside benchmark timing Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index c77ee467fe5..2f26e1b0f12 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -81,7 +81,7 @@ fn write(bencher: Bencher, nullable: bool) { fn full(bencher: Bencher, nullable: bool) { let source = compressed(nullable); let mut check = SESSION.create_execution_ctx(); - assert_arrays_eq!(source.clone(), input(nullable), &mut check); + assert_arrays_eq!(source, input(nullable), &mut check); drop(check); bencher .with_inputs(|| SESSION.create_execution_ctx()) From ceb3ecf5dc9e12d0a4d74b621d89dfd9b39832cf Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 14:27:42 -0400 Subject: [PATCH 07/10] Cover contiguous, mixed and narrow-owner repeated selection controls Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index 2f26e1b0f12..7282074faed 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -94,6 +94,12 @@ fn full(bencher: Bencher, nullable: bool) { } const SELECTIONS: &[(&str, bool)] = &[ + ("contiguous", false), + ("contiguous", true), + ("mixed", false), + ("mixed", true), + ("narrow_repeated", false), + ("narrow_repeated", true), ("sparse", false), ("sparse", true), ("broad", false), @@ -109,7 +115,21 @@ const SELECTIONS: &[(&str, bool)] = &[ #[divan::bench(args = SELECTIONS)] fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { let source = compressed(nullable); + let source = if kind == "narrow_repeated" { + source.slice(ROWS - 128..ROWS).vortex_expect("narrow view") + } else { + source + }; + let indices = match kind { + "contiguous" => PrimitiveArray::from_iter(1..ROWS as u64 - 1).into_array(), + "mixed" => PrimitiveArray::from_iter( + (0..524288u64).map(|i| if i < 2 { i } else { (i * 32749) % ROWS as u64 }), + ) + .into_array(), + "narrow_repeated" => { + PrimitiveArray::from_iter((0..524288u64).map(|i| (i % 2) * 127)).into_array() + } "sparse" => { PrimitiveArray::from_iter((0..128u64).map(|i| (i * 32749) % ROWS as u64)).into_array() } @@ -130,6 +150,13 @@ fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { }; let mut check = SESSION.create_execution_ctx(); let reference = input(nullable).into_array(); + let reference = if kind == "narrow_repeated" { + reference + .slice(ROWS - 128..ROWS) + .vortex_expect("primitive narrow view") + } else { + reference + }; let actual = if kind == "near_full" { source.slice(1..ROWS - 1) } else { From a3b7e454ca6eb60f220c8e3afdffcaee6db91c66 Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 14:42:05 -0400 Subject: [PATCH 08/10] Import referenced modules at file scope and document page coordinates Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index 7282074faed..ab5133c2919 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -10,6 +10,7 @@ use std::hint::black_box; use std::sync::LazyLock; use divan::Bencher; +use divan::main as run_benchmarks; use vortex_array::ArrayDeserialization; use vortex_array::ArrayPlugin; use vortex_array::ArrayRef; @@ -39,7 +40,7 @@ static SESSION: LazyLock = LazyLock::new(|| { }); fn main() { - divan::main(); + run_benchmarks(); } fn input(nullable: bool) -> PrimitiveArray { From 7dfbf875f082f809df04b719c02766ca6c98da68 Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 16:32:31 -0400 Subject: [PATCH 09/10] Calibrate narrow repeated output to a fixed tens-of-milliseconds workload Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index ab5133c2919..57d9261f57a 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -129,7 +129,7 @@ fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { ) .into_array(), "narrow_repeated" => { - PrimitiveArray::from_iter((0..524288u64).map(|i| (i % 2) * 127)).into_array() + PrimitiveArray::from_iter((0..33_554_432u64).map(|i| (i % 2) * 127)).into_array() } "sparse" => { PrimitiveArray::from_iter((0..128u64).map(|i| (i * 32749) % ROWS as u64)).into_array() From 2736d4cf926627c265bb8eba9a270183691d2a05 Mon Sep 17 00:00:00 2001 From: Daniel King Date: Mon, 26 Oct 2026 16:41:48 -0400 Subject: [PATCH 10/10] Fix the final narrow-output calibration equally for every implementation Signed-off-by: Daniel King --- encodings/pco/benches/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encodings/pco/benches/selection.rs b/encodings/pco/benches/selection.rs index 57d9261f57a..6154f959594 100644 --- a/encodings/pco/benches/selection.rs +++ b/encodings/pco/benches/selection.rs @@ -129,7 +129,7 @@ fn selection(bencher: Bencher, (kind, nullable): (&str, bool)) { ) .into_array(), "narrow_repeated" => { - PrimitiveArray::from_iter((0..33_554_432u64).map(|i| (i % 2) * 127)).into_array() + PrimitiveArray::from_iter((0..4_194_304u64).map(|i| (i % 2) * 127)).into_array() } "sparse" => { PrimitiveArray::from_iter((0..128u64).map(|i| (i * 32749) % ROWS as u64)).into_array()