From 3cd9a97f872ca10922706efeb072a8abd3496131 Mon Sep 17 00:00:00 2001 From: Connor Tsui Date: Tue, 3 Nov 2026 15:28:51 -0500 Subject: [PATCH] Remove redundant microbenchmarks Keep the maintained Vortex entry points while removing third-party timing baselines, explicit decode-first comparison paths, and an exact duplicate. Measure integer Widen decoding separately instead of repeating every operation over canonical and explicitly decoded inputs. Update the affected benchmark sharding estimates and drop unused benchmark dependencies. Preserve distinct operations, the decimal Widen target, and the CodSpeed exclusions already on develop. Signed-off-by: Connor Tsui --- Cargo.lock | 6 - .../fastlanes/benches/bitpack_compare.rs | 35 +-- .../fastlanes/benches/bitpacking_take.rs | 47 +-- scripts/codspeed_bench_counts.json | 20 +- vortex-array/benches/filter_fixed_width.rs | 4 +- .../piecewise_sequence_take_primitive.rs | 53 ---- vortex-array/benches/search_sorted.rs | 19 +- vortex-array/benches/widen.rs | 281 +++++++----------- vortex-buffer/benches/vortex_bitbuffer.rs | 146 --------- vortex-buffer/benches/vortex_buffer.rs | 49 +-- vortex-compute/Cargo.toml | 5 - vortex-compute/benches/lane_kernels.rs | 104 +------ vortex-row/Cargo.toml | 1 - vortex-row/benches/row_encode.rs | 83 +----- 14 files changed, 136 insertions(+), 717 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efd8fcf1a69..2f0171d8a02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11697,11 +11697,6 @@ dependencies = [ name = "vortex-compute" version = "0.1.0" dependencies = [ - "arrow-arith 59.3.2", - "arrow-array 59.3.2", - "arrow-buffer 59.3.2", - "arrow-cast 59.3.2", - "arrow-schema 59.3.2", "codspeed-divan-compat", "mimalloc", "num-traits", @@ -12409,7 +12404,6 @@ version = "0.1.0" dependencies = [ "arrow-array 59.3.2", "arrow-row 59.3.2", - "arrow-schema 59.3.2", "codspeed-divan-compat", "mimalloc", "num-traits", diff --git a/encodings/fastlanes/benches/bitpack_compare.rs b/encodings/fastlanes/benches/bitpack_compare.rs index 200916c0e7c..9cfc5cb5201 100644 --- a/encodings/fastlanes/benches/bitpack_compare.rs +++ b/encodings/fastlanes/benches/bitpack_compare.rs @@ -1,9 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Compare an already-packed `BitPackedArray` against a constant value. Compares the -//! out-of-range fast path (constant outside `[0, 2^bit_width - 1]`) against an explicit -//! "decompress, then compare" baseline. +//! Compare an already-packed `BitPackedArray` against a constant outside +//! `[0, 2^bit_width - 1]`, which the bit width alone decides without unpacking. //! //! Sized to finish quickly. Run with `cargo bench -p vortex-fastlanes --bench bitpack_compare`. @@ -115,22 +114,6 @@ fn fast_eq_out_of_range(bencher: Bencher) { }); } -#[divan::bench] -fn baseline_eq(bencher: Bencher) { - let (array, rhs, mut ctx) = build_inputs(); - bencher.bench_local(|| { - // What the fallback would do: materialize the unpacked primitive, then run Arrow - // compare on it. - let primitive = array.clone().execute::(&mut ctx).unwrap(); - primitive - .into_array() - .binary(rhs.clone(), Operator::Eq) - .unwrap() - .execute::(&mut ctx) - .unwrap() - }); -} - #[divan::bench] fn fast_lt_out_of_range(bencher: Bencher) { let (array, rhs, mut ctx) = build_inputs(); @@ -143,17 +126,3 @@ fn fast_lt_out_of_range(bencher: Bencher) { .unwrap() }); } - -#[divan::bench] -fn baseline_lt(bencher: Bencher) { - let (array, rhs, mut ctx) = build_inputs(); - bencher.bench_local(|| { - let primitive = array.clone().execute::(&mut ctx).unwrap(); - primitive - .into_array() - .binary(rhs.clone(), Operator::Lt) - .unwrap() - .execute::(&mut ctx) - .unwrap() - }); -} diff --git a/encodings/fastlanes/benches/bitpacking_take.rs b/encodings/fastlanes/benches/bitpacking_take.rs index 88576fdf669..6146f82fda2 100644 --- a/encodings/fastlanes/benches/bitpacking_take.rs +++ b/encodings/fastlanes/benches/bitpacking_take.rs @@ -429,8 +429,7 @@ fn patched_take_10k_adversarial(bencher: Bencher) { }) } -// These are the element runs emitted by a sparse FSL gather. The baseline materializes those -// runs inside the measured operation, reproducing the previous BitPacked take path. +// These are the element runs emitted by a sparse FSL gather. fn embedding_runs_fixture(dimension: usize, ptype: PType, bit_width: u8) -> (ArrayRef, ArrayRef) { const ROWS: usize = 512; const SELECTED: usize = 16; @@ -464,25 +463,13 @@ fn embedding_runs_fixture(dimension: usize, ptype: PType, bit_width: u8) -> (Arr #[divan::bench(args = [768, 1024, 1536])] fn embedding_runs(bencher: Bencher, dimension: usize) { let (packed, indices) = embedding_runs_fixture(dimension, PType::U32, 16); - bench_piecewise_take(bencher, packed, indices, false); -} - -#[divan::bench(args = [768, 1024, 1536])] -fn embedding_runs_expanded_baseline(bencher: Bencher, dimension: usize) { - let (packed, indices) = embedding_runs_fixture(dimension, PType::U32, 16); - bench_piecewise_take(bencher, packed, indices, true); + bench_piecewise_take(bencher, packed, indices); } #[divan::bench(args = [512, 1024, 2048, 4096])] fn embedding_code_runs(bencher: Bencher, dimension: usize) { let (packed, indices) = embedding_runs_fixture(dimension, PType::U8, 4); - bench_piecewise_take(bencher, packed, indices, false); -} - -#[divan::bench(args = [512, 1024, 2048, 4096])] -fn embedding_code_runs_expanded_baseline(bencher: Bencher, dimension: usize) { - let (packed, indices) = embedding_runs_fixture(dimension, PType::U8, 4); - bench_piecewise_take(bencher, packed, indices, true); + bench_piecewise_take(bencher, packed, indices); } // Short runs share chunks; long runs visit distant chunks. Neither crosses the full-decode @@ -514,13 +501,7 @@ fn fragmented_runs_fixture(run_length: usize) -> (ArrayRef, ArrayRef) { #[divan::bench(args = [1, 2, 7, 8, 64, 512, 1024])] fn fragmented_runs(bencher: Bencher, run_length: usize) { let (packed, indices) = fragmented_runs_fixture(run_length); - bench_piecewise_take(bencher, packed, indices, false); -} - -#[divan::bench(args = [1, 2, 7, 8, 64, 512, 1024])] -fn fragmented_runs_expanded_baseline(bencher: Bencher, run_length: usize) { - let (packed, indices) = fragmented_runs_fixture(run_length); - bench_piecewise_take(bencher, packed, indices, true); + bench_piecewise_take(bencher, packed, indices); } // Isolated runs cannot amortize a whole-chunk decode across neighboring runs. Offsets near @@ -565,30 +546,16 @@ fn isolated_runs(bencher: Bencher, (run_length, offset): (usize, ) .unwrap() .into_array(); - bench_piecewise_take(bencher, packed, indices, false); + bench_piecewise_take(bencher, packed, indices); } -fn bench_piecewise_take( - bencher: Bencher, - packed: ArrayRef, - indices: ArrayRef, - expanded_baseline: bool, -) { +fn bench_piecewise_take(bencher: Bencher, packed: ArrayRef, indices: ArrayRef) { bencher .counter(ItemsCount::new(indices.len())) .with_inputs(|| (indices.clone(), SESSION.create_execution_ctx())) .bench_refs(|(indices, ctx)| { - let indices = if expanded_baseline { - indices - .clone() - .execute::(ctx) - .unwrap() - .into_array() - } else { - indices.clone() - }; packed - .take(indices) + .take(indices.clone()) .unwrap() .execute::(ctx) .unwrap() diff --git a/scripts/codspeed_bench_counts.json b/scripts/codspeed_bench_counts.json index c356c1d946f..87ccb9e575b 100644 --- a/scripts/codspeed_bench_counts.json +++ b/scripts/codspeed_bench_counts.json @@ -30,7 +30,7 @@ "expr_large_struct_pack": 4, "expr_optimize": 1, "filter_bool": 61, - "filter_fixed_width": 31, + "filter_fixed_width": 30, "filter_fsl": 4, "filter_list": 12, "interleave": 18, @@ -43,7 +43,7 @@ "listview_rebuild": 9, "listview_zip": 2, "patches_lookup": 10, - "piecewise_sequence_take_primitive": 15, + "piecewise_sequence_take_primitive": 10, "primitive_zip": 2, "probe": 6, "row_fn_bool_retry": 0, @@ -60,7 +60,7 @@ "scalar_construction": 10, "scalar_fn_probe": 8, "scalar_subtract": 0, - "search_sorted": 2, + "search_sorted": 1, "slice_dict_primitive": 3, "take_chunked": 27, "take_filter": 32, @@ -74,7 +74,7 @@ "varbinview_fill_null": 2, "varbinview_try_new": 6, "varbinview_zip": 2, - "widen": 46, + "widen": 17, "widen_decimal": 6 }, "vortex-buffer": { @@ -82,8 +82,8 @@ "collect_bool": 4, "cpu_dispatch": 6, "take_bits": 0, - "vortex_bitbuffer": 101, - "vortex_buffer": 40 + "vortex_bitbuffer": 58, + "vortex_buffer": 34 }, "vortex-compressor": { "dict_encode": 0, @@ -92,7 +92,7 @@ "stats_calc": 0 }, "vortex-compute": { - "lane_kernels": 10 + "lane_kernels": 7 }, "vortex-datetime-parts": { "split_temporal": 4 @@ -108,10 +108,10 @@ "vortex-fastlanes": { "bitpack_blocked": 0, "bitpack_blocked_decompress": 0, - "bitpack_compare": 4, + "bitpack_compare": 2, "bitpack_compare_sweep": 232, "bitpacking_filter": 0, - "bitpacking_take": 54, + "bitpacking_take": 40, "canonicalize_bench": 32, "cast_bitpacked": 6, "compute_between": 33, @@ -144,7 +144,7 @@ "scalar": 4 }, "vortex-row": { - "row_encode": 10 + "row_encode": 5 }, "vortex-sparse": { "sparse_canonical": 4, diff --git a/vortex-array/benches/filter_fixed_width.rs b/vortex-array/benches/filter_fixed_width.rs index 12d98610e77..b8799dfe558 100644 --- a/vortex-array/benches/filter_fixed_width.rs +++ b/vortex-array/benches/filter_fixed_width.rs @@ -62,12 +62,11 @@ const CACHED_DENSITIES: &[f64] = &[0.01, 0.1]; #[derive(Clone, Copy, Debug)] enum Pattern { - Random, Runs, Contiguous, } -const PATTERNS: &[Pattern] = &[Pattern::Random, Pattern::Runs, Pattern::Contiguous]; +const PATTERNS: &[Pattern] = &[Pattern::Runs, Pattern::Contiguous]; fn random_mask(len: usize, density: f64) -> Mask { let threshold = (density * u64::MAX as f64) as u64; @@ -82,7 +81,6 @@ fn random_mask(len: usize, density: f64) -> Mask { fn pattern_mask(len: usize, pattern: Pattern) -> Mask { match pattern { - Pattern::Random => random_mask(len, 0.5), Pattern::Runs => Mask::from_iter((0..len).map(|index| (index / 32).is_multiple_of(2))), Pattern::Contiguous => Mask::from_slices(len, vec![(len / 4, len * 3 / 4)]), } diff --git a/vortex-array/benches/piecewise_sequence_take_primitive.rs b/vortex-array/benches/piecewise_sequence_take_primitive.rs index fd7001701ac..1a17757d3cb 100644 --- a/vortex-array/benches/piecewise_sequence_take_primitive.rs +++ b/vortex-array/benches/piecewise_sequence_take_primitive.rs @@ -2,10 +2,6 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors //! Primitive take benchmarks for [`PiecewiseSequenceArray`] length representations. -//! -//! The `reify_constant_lengths_then_take` case simulates removing the constant-length -//! specialization: it starts with constant lengths, materializes them to a dense primitive array, -//! and then executes the primitive take through the non-constant-length path. #![allow(clippy::cast_possible_truncation)] #![expect(clippy::unwrap_used)] @@ -89,55 +85,6 @@ fn prebuilt_dense_lengths(bencher: Bencher, run_length: usize) { }); } -#[divan::bench(args = RUN_LENGTHS)] -fn reify_constant_lengths_then_take(bencher: Bencher, run_length: usize) { - let values = values(); - let run_count = run_count(run_length); - let starts = starts(run_count, run_length).into_array(); - let lengths = lengths_constant(run_length, run_count); - let multipliers = ConstantArray::new(1u64, run_count).into_array(); - - bencher - .counter(BytesCount::of_many::(OUTPUT_LEN)) - .with_inputs(|| { - ( - &values, - starts.clone(), - lengths.clone(), - multipliers.clone(), - SESSION.create_execution_ctx(), - ) - }) - .bench_refs(|(values, starts, lengths, multipliers, ctx)| { - let lengths = lengths - .clone() - .execute::(ctx) - .unwrap() - .into_array(); - - // SAFETY: starts, reified lengths, and unit multipliers have the same run count, are - // non-null unsigned integer arrays, and expand to OUTPUT_LEN by construction. - let indices = unsafe { - PiecewiseSequenceArray::new_unchecked( - starts.clone(), - lengths, - multipliers.clone(), - OUTPUT_LEN, - ) - } - .into_array(); - - divan::black_box( - values - .clone() - .take(indices) - .unwrap() - .execute::(ctx) - .unwrap(), - ); - }); -} - fn values() -> ArrayRef { PrimitiveArray::from_iter((0..SOURCE_LEN).map(|idx| idx as i64)).into_array() } diff --git a/vortex-array/benches/search_sorted.rs b/vortex-array/benches/search_sorted.rs index 4f339893fb7..cd7d26028bf 100644 --- a/vortex-array/benches/search_sorted.rs +++ b/vortex-array/benches/search_sorted.rs @@ -20,25 +20,10 @@ fn main() { } /// One search over 65536 elements is ~16 comparisons, small enough that the measurement is mostly -/// harness overhead and the two implementations below become indistinguishable. Search a batch per -/// iteration instead; the targets differ, so the calls cannot be folded together. +/// harness overhead. Search a batch per iteration instead; the targets differ, so the calls cannot +/// be folded together. const SEARCH_TARGETS: usize = 64; -#[cfg(not(codspeed))] -#[divan::bench] -fn binary_search_std(bencher: Bencher) { - let (sorted_array, targets) = fixture(); - bencher - .with_inputs(|| (&sorted_array, &targets)) - .bench_refs(|(array, targets)| { - let mut found = 0; - for target in targets.iter() { - found += array.binary_search(target).unwrap_or_else(|idx| idx); - } - found - }); -} - #[divan::bench] fn binary_search_vortex(bencher: Bencher) { let (sorted_array, targets) = fixture(); diff --git a/vortex-array/benches/widen.rs b/vortex-array/benches/widen.rs index 7a24650df9d..2bb0fee90c3 100644 --- a/vortex-array/benches/widen.rs +++ b/vortex-array/benches/widen.rs @@ -1,15 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors -//! Compute on integer `Widen` arrays, against the canonical array with the same values. +//! Compute on integer `Widen` arrays. //! -//! Each case runs one operation on the inputs of [`Input`], which hold the same logical `i64` -//! values. `slice` and `add_canonical` omit `canonical`. A kernel for `Widen` changes only the -//! `widen` case. The `canonical` case measures the canonical path, and the `decoded` case -//! measures the fallback that decodes the `Widen` first. So the `canonical` and `decoded` cases -//! must stay flat when a kernel for `Widen` lands. +//! Each case runs one operation on a `Widen` with logical dtype `i64` over an `i16` child. +//! The `decode` case measures materialization separately from the operations that can use +//! the narrower child directly. -use std::fmt; use std::sync::LazyLock; use divan::Bencher; @@ -56,11 +53,10 @@ fn main() { // The first execution initializes process-wide state. Without this call, that work lands in the // single measured iteration of the first benchmark under the CodSpeed simulation. - let values = Values::random(0, LEN); - Input::Decoded.prepare( - values.array(Input::Widen), - &mut SESSION.create_execution_ctx(), - ); + Values::random(0, LEN) + .widen() + .execute::(&mut SESSION.create_execution_ctx()) + .vortex_expect("a Widen of i64 always executes to a PrimitiveArray"); divan::main(); } @@ -74,7 +70,7 @@ static SESSION: LazyLock = LazyLock::new(|| { .with::() }); -// The canonical input holds 256 KiB. The inputs are sized so that every case does a few +// The decoded values hold 256 KiB. The inputs are sized so that every case does a few // microseconds of real work above the CodSpeed harness floor, while the slowest case stays far // under 1 ms. const LEN: usize = 32_768; @@ -90,43 +86,6 @@ const INCLUSIVE: BetweenOptions = BetweenOptions { upper_strict: StrictComparison::NonStrict, }; -#[derive(Clone, Copy, Debug)] -enum Input { - /// The canonical `i64` array with the same values. - Canonical, - /// A `Widen` with logical dtype `i64` over an `i16` child. - Widen, - /// The `Widen`, executed to a `PrimitiveArray` inside the timed region, then the operation. - Decoded, -} - -const INPUTS: &[Input] = &[Input::Canonical, Input::Widen, Input::Decoded]; -const WIDEN_INPUTS: &[Input] = &[Input::Widen, Input::Decoded]; - -impl Input { - /// Returns `array` as the operation receives it. For `Decoded`, this decodes the `Widen`, so - /// it must run inside the timed region. - fn prepare(self, array: ArrayRef, ctx: &mut ExecutionCtx) -> ArrayRef { - match self { - Self::Canonical | Self::Widen => array, - Self::Decoded => array - .execute::(ctx) - .vortex_expect("a Widen of i64 always executes to a PrimitiveArray") - .into_array(), - } - } -} - -impl fmt::Display for Input { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::Canonical => "canonical", - Self::Widen => "widen", - Self::Decoded => "decoded", - }) - } -} - /// The buffers of one input, shared by every iteration. /// /// `sum` caches its result as a statistic on the array that it reads, and later kernels read @@ -164,24 +123,25 @@ impl Values { } } - /// Builds a new array for `input`. `Decoded` gets the `Widen`, which [`Input::prepare`] - /// decodes. - fn array(&self, input: Input) -> ArrayRef { - let validity = self - .validity + fn validity(&self) -> Validity { + self.validity .clone() - .map_or(Validity::NonNullable, Validity::from); - - match input { - Input::Canonical => PrimitiveArray::new(self.wide.clone(), validity).into_array(), - Input::Widen | Input::Decoded => { - let dtype = DType::Primitive(PType::I64, validity.nullability()); - let values = PrimitiveArray::new(self.narrow.clone(), validity).into_array(); - WidenArray::try_new(values, dtype) - .vortex_expect("i16 values with the nullability of the dtype can widen to i64") - .into_array() - } - } + .map_or(Validity::NonNullable, Validity::from) + } + + /// Builds a new `Widen` of the values. + fn widen(&self) -> ArrayRef { + let validity = self.validity(); + let dtype = DType::Primitive(PType::I64, validity.nullability()); + let values = PrimitiveArray::new(self.narrow.clone(), validity).into_array(); + WidenArray::try_new(values, dtype) + .vortex_expect("i16 values with the nullability of the dtype can widen to i64") + .into_array() + } + + /// Builds a new canonical `i64` array of the values. + fn canonical(&self) -> ArrayRef { + PrimitiveArray::new(self.wide.clone(), self.validity()).into_array() } } @@ -213,67 +173,55 @@ fn execute_canonical(array: VortexResult, ctx: &mut ExecutionCtx) -> C .vortex_expect("every operation executes to canonical for the benchmark inputs") } -#[divan::bench(args = INPUTS)] -fn compare_in_range(bencher: Bencher, input: Input) { +#[divan::bench] +fn compare_in_range(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || (values.array(input), constant(0)), - |(array, zero), ctx| { - let array = input.prepare(array, ctx); - execute_canonical(array.binary(zero, Operator::Lt), ctx) - }, + || (values.widen(), constant(0)), + |(array, zero), ctx| execute_canonical(array.binary(zero, Operator::Lt), ctx), ); } -#[divan::bench(args = INPUTS)] -fn between_in_range(bencher: Bencher, input: Input) { +#[divan::bench] +fn between_in_range(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || (values.array(input), constant(-1_000), constant(1_000)), - |(array, lower, upper), ctx| { - let array = input.prepare(array, ctx); - execute_canonical(array.between(lower, upper, INCLUSIVE), ctx) - }, + || (values.widen(), constant(-1_000), constant(1_000)), + |(array, lower, upper), ctx| execute_canonical(array.between(lower, upper, INCLUSIVE), ctx), ); } -#[divan::bench(args = INPUTS)] -fn filter(bencher: Bencher, input: Input) { +#[divan::bench] +fn filter(bencher: Bencher) { let values = Values::random(0, LEN); let selection = random_bits(2, 0.5); bench_case( bencher, - || (values.array(input), Mask::from_buffer(selection.clone())), - |(array, mask), ctx| execute_canonical(input.prepare(array, ctx).filter(mask), ctx), + || (values.widen(), Mask::from_buffer(selection.clone())), + |(array, mask), ctx| execute_canonical(array.filter(mask), ctx), ); } -/// Gathers [`LEN`] / 4 rows from [`LEN`] values. -/// -/// The `widen` input gathers the `i16` values and then widens only the gathered rows. The -/// `decoded` input widens every value before the gather. -#[divan::bench(args = INPUTS)] -fn take_few_indices(bencher: Bencher, input: Input) { +/// Gathers [`LEN`] / 4 rows from [`LEN`] values, so only the gathered rows are widened. +#[divan::bench] +fn take_few_indices(bencher: Bencher) { let values = Values::random(0, LEN); let mut rng = StdRng::seed_from_u64(3); let len = u32::try_from(LEN).vortex_expect("LEN fits u32"); let indices: Buffer = (0..LEN / 4).map(|_| rng.random_range(0..len)).collect(); bench_case( bencher, - || (values.array(input), indices.clone().into_array()), - |(array, indices), ctx| execute_canonical(input.prepare(array, ctx).take(indices), ctx), + || (values.widen(), indices.clone().into_array()), + |(array, indices), ctx| execute_canonical(array.take(indices), ctx), ); } /// Gathers [`LEN`] rows from [`FEW_VALUES_LEN`] values, as a dictionary gathers its values by its -/// codes. -/// -/// The `widen` input gathers the `i16` values and then widens every gathered row. The `decoded` -/// input widens only the few values before the gather. -#[divan::bench(args = INPUTS)] -fn take_many_indices(bencher: Bencher, input: Input) { +/// codes, so every gathered row is widened. +#[divan::bench] +fn take_many_indices(bencher: Bencher) { let values = Values::random(0, FEW_VALUES_LEN); let mut rng = StdRng::seed_from_u64(3); let indices: Buffer = (0..LEN) @@ -284,115 +232,115 @@ fn take_many_indices(bencher: Bencher, input: Input) { .collect(); bench_case( bencher, - || (values.array(input), indices.clone().into_array()), - |(array, indices), ctx| execute_canonical(input.prepare(array, ctx).take(indices), ctx), + || (values.widen(), indices.clone().into_array()), + |(array, indices), ctx| execute_canonical(array.take(indices), ctx), ); } -/// Omits the `canonical` input, because a slice of a canonical array copies nothing. -#[divan::bench(args = WIDEN_INPUTS)] -fn slice(bencher: Bencher, input: Input) { +/// Executes the `Widen` to canonical, which every operation without a kernel does first. +#[divan::bench] +fn decode(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), - |array, ctx| execute_canonical(input.prepare(array, ctx).slice(LEN / 4..3 * LEN / 4), ctx), + || values.widen(), + |array, ctx| { + array + .execute::(ctx) + .vortex_expect("a Widen always executes to canonical") + }, ); } -#[divan::bench(args = INPUTS)] -fn sum(bencher: Bencher, input: Input) { +#[divan::bench] +fn slice(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), - |array, ctx| { - sum::sum(&input.prepare(array, ctx), ctx) - .vortex_expect("sum is valid for every benchmark input") - }, + || values.widen(), + |array, ctx| execute_canonical(array.slice(LEN / 4..3 * LEN / 4), ctx), + ); +} + +#[divan::bench] +fn sum(bencher: Bencher) { + let values = Values::random(0, LEN); + bench_case( + bencher, + || values.widen(), + |array, ctx| sum::sum(&array, ctx).vortex_expect("sum is valid for every benchmark input"), ); } -#[divan::bench(args = INPUTS)] -fn min_max(bencher: Bencher, input: Input) { +#[divan::bench] +fn min_max(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), + || values.widen(), |array, ctx| { - let array = input.prepare(array, ctx); min_max::min_max(&array, ctx, NumericalAggregateOpts::default()) .vortex_expect("min_max is valid for every benchmark input") }, ); } -#[divan::bench(args = INPUTS)] -fn add_constant(bencher: Bencher, input: Input) { +#[divan::bench] +fn add_constant(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || (values.array(input), constant(1)), - |(array, one), ctx| { - let array = input.prepare(array, ctx); - execute_canonical(array.binary(one, Operator::Add), ctx) - }, + || (values.widen(), constant(1)), + |(array, one), ctx| execute_canonical(array.binary(one, Operator::Add), ctx), ); } -#[divan::bench(args = INPUTS)] -fn add_widen(bencher: Bencher, input: Input) { +#[divan::bench] +fn add_widen(bencher: Bencher) { let lhs = Values::random(0, LEN); let rhs = Values::random(1, LEN); bench_case( bencher, - || (lhs.array(input), rhs.array(input)), - |(lhs, rhs), ctx| { - let lhs = input.prepare(lhs, ctx); - let rhs = input.prepare(rhs, ctx); - execute_canonical(lhs.binary(rhs, Operator::Add), ctx) - }, + || (lhs.widen(), rhs.widen()), + |(lhs, rhs), ctx| execute_canonical(lhs.binary(rhs, Operator::Add), ctx), ); } /// Adds a canonical `i64` array, which a `Widen` kernel cannot read at the child width. -#[divan::bench(args = WIDEN_INPUTS)] -fn add_canonical(bencher: Bencher, input: Input) { +#[divan::bench] +fn add_canonical(bencher: Bencher) { let lhs = Values::random(0, LEN); let rhs = Values::random(1, LEN); bench_case( bencher, - || (lhs.array(input), rhs.array(Input::Canonical)), - |(lhs, rhs), ctx| { - let lhs = input.prepare(lhs, ctx); - execute_canonical(lhs.binary(rhs, Operator::Add), ctx) - }, + || (lhs.widen(), rhs.canonical()), + |(lhs, rhs), ctx| execute_canonical(lhs.binary(rhs, Operator::Add), ctx), ); } -#[divan::bench(args = INPUTS)] -fn cast_f64(bencher: Bencher, input: Input) { +#[divan::bench] +fn cast_f64(bencher: Bencher) { let values = Values::random(0, LEN); bench_case( bencher, - || values.array(input), - |array, ctx| execute_canonical(input.prepare(array, ctx).cast(F64), ctx), + || values.widen(), + |array, ctx| execute_canonical(array.cast(F64), ctx), ); } /// Fills the nulls with a value that fits `i32` but not the `i16` child. -#[divan::bench(args = INPUTS)] -fn fill_null_out_of_range(bencher: Bencher, input: Input) { +#[divan::bench] +fn fill_null_out_of_range(bencher: Bencher) { let values = Values::random(0, LEN).with_validity(random_bits(4, 0.9)); bench_case( bencher, - || values.array(input), - |array, ctx| execute_canonical(input.prepare(array, ctx).fill_null(100_000i64), ctx), + || values.widen(), + |array, ctx| execute_canonical(array.fill_null(100_000i64), ctx), ); } -#[divan::bench(args = INPUTS)] -fn zip(bencher: Bencher, input: Input) { +#[divan::bench] +fn zip(bencher: Bencher) { let if_true = Values::random(0, LEN); let if_false = Values::random(1, LEN); let selection = random_bits(2, 0.5); @@ -400,33 +348,20 @@ fn zip(bencher: Bencher, input: Input) { bencher, || { let mask = selection.clone().into_array(); - (mask, if_true.array(input), if_false.array(input)) - }, - |(mask, if_true, if_false), ctx| { - let if_true = input.prepare(if_true, ctx); - let if_false = input.prepare(if_false, ctx); - execute_canonical(mask.zip(if_true, if_false), ctx) + (mask, if_true.widen(), if_false.widen()) }, + |(mask, if_true, if_false), ctx| execute_canonical(mask.zip(if_true, if_false), ctx), ); } -/// Canonicalizes a chunked array of four chunks, whose chunks are `Widen` for the `widen` input. -#[divan::bench(args = INPUTS)] -fn chunked(bencher: Bencher, input: Input) { +/// Canonicalizes a chunked array of four `Widen` chunks. +#[divan::bench] +fn chunked(bencher: Bencher) { let chunks: Vec = (0..4).map(|seed| Values::random(seed, LEN / 4)).collect(); bench_case( bencher, - || { - chunks - .iter() - .map(|chunk| chunk.array(input)) - .collect::>() - }, + || chunks.iter().map(Values::widen).collect::>(), |chunks, ctx| { - let chunks: Vec = chunks - .into_iter() - .map(|chunk| input.prepare(chunk, ctx)) - .collect(); execute_canonical( ChunkedArray::try_new(chunks, I64).map(IntoArray::into_array), ctx, @@ -436,8 +371,8 @@ fn chunked(bencher: Bencher, input: Input) { } /// Merges two inputs row by row. -#[divan::bench(args = INPUTS)] -fn interleave(bencher: Bencher, input: Input) { +#[divan::bench] +fn interleave(bencher: Bencher) { let first = Values::random(0, LEN / 2); let second = Values::random(1, LEN / 2); let array_indices: Buffer = (0..LEN / 2).flat_map(|_| [0, 1]).collect(); @@ -445,15 +380,13 @@ fn interleave(bencher: Bencher, input: Input) { bench_case( bencher, || { - let values = [first.array(input), second.array(input)]; ( - values, + vec![first.widen(), second.widen()], array_indices.clone().into_array(), row_indices.clone().into_array(), ) }, - |([first, second], array_indices, row_indices), ctx| { - let values = vec![input.prepare(first, ctx), input.prepare(second, ctx)]; + |(values, array_indices, row_indices), ctx| { let interleaved = InterleaveArray::try_new(values, array_indices, row_indices); execute_canonical(interleaved.map(IntoArray::into_array), ctx) }, diff --git a/vortex-buffer/benches/vortex_bitbuffer.rs b/vortex-buffer/benches/vortex_bitbuffer.rs index 48d48d83d85..3a95453a0e4 100644 --- a/vortex-buffer/benches/vortex_bitbuffer.rs +++ b/vortex-buffer/benches/vortex_bitbuffer.rs @@ -3,8 +3,6 @@ use std::iter::Iterator; -use arrow_buffer::BooleanBuffer; -use arrow_buffer::BooleanBufferBuilder; use divan::Bencher; use mimalloc::MiMalloc; use vortex_buffer::BitBuffer; @@ -32,15 +30,6 @@ fn main() { divan::main(); } -/// Wraps an arrow buffer so Divan can provide a nice name -pub struct Arrow(T); - -impl FromIterator for Arrow { - fn from_iter>(iter: I) -> Self { - Self(BooleanBuffer::from_iter(iter)) - } -} - const INPUT_SIZE: &[usize] = &[128, 1024, 2048, 16_384, 65_536]; #[inline] @@ -48,12 +37,6 @@ fn true_count_pattern(i: usize) -> bool { (i.is_multiple_of(3)) ^ (i.is_multiple_of(11)) } -#[cfg(not(codspeed))] -#[divan::bench(args = INPUT_SIZE)] -fn from_iter_arrow(n: usize) -> Arrow { - Arrow::::from_iter((0..n).map(|i| i % 2 == 0)) -} - #[divan::bench(args = INPUT_SIZE)] fn from_iter_bit_buffer(n: usize) -> BitBuffer { BitBuffer::from_iter((0..n).map(|i| i % 2 == 0)) @@ -70,17 +53,6 @@ fn append_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn append_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| (Arrow(BooleanBufferBuilder::new(length)), length)) - .bench_refs(|(buffer, length)| { - for idx in 0..*length { - buffer.0.append(idx % 2 == 0); - } - }); -} - const CHUNKED_APPEND_INPUT_SIZE: &[usize] = &[2_048, 16_384, 65_536]; #[divan::bench(args = CHUNKED_APPEND_INPUT_SIZE)] @@ -94,17 +66,6 @@ fn append_n_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = CHUNKED_APPEND_INPUT_SIZE)] -fn append_n_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| (Arrow(BooleanBufferBuilder::new(length)), length, true)) - .bench_refs(|(buffer, length, boolean)| { - for _ in 0..100 { - buffer.0.append_n(*length / 100, *boolean); - } - }); -} - #[divan::bench(args = CHUNKED_APPEND_INPUT_SIZE)] fn append_buffer_vortex_buffer(bencher: Bencher, length: usize) { bencher @@ -120,29 +81,6 @@ fn append_buffer_vortex_buffer(bencher: Bencher, length: usize) { }); } -/// Arrow has no bulk append from a `BooleanBuffer`, so this appends bit-by-bit and runs an -/// order of magnitude slower than the vortex bulk path; cap the top size accordingly. -const APPEND_BUFFER_ARROW_INPUT_SIZE: &[usize] = &[2_048, 16_384, 32_768]; - -#[divan::bench(args = APPEND_BUFFER_ARROW_INPUT_SIZE)] -fn append_buffer_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| { - let source = Arrow(BooleanBuffer::from_iter( - (0..length / 100).map(|i| i % 2 == 0), - )); - let dest = Arrow(BooleanBufferBuilder::new(length)); - (source, dest) - }) - .bench_refs(|(source, dest)| { - for _ in 0..100 { - for value in source.0.iter() { - dest.0.append(value); - } - } - }); -} - #[divan::bench(args = INPUT_SIZE)] fn value_vortex_buffer(bencher: Bencher, length: usize) { let buffer = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -153,16 +91,6 @@ fn value_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn value_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - for idx in 0..length { - divan::black_box(buffer.0.value(idx)); - } - }); -} - /// Slicing only adjusts an offset, a length and a refcount, so its cost is independent of buffer /// length. Measure one length, with enough slices per iteration to stay above harness overhead. const SLICE_ITERS: usize = 64; @@ -181,22 +109,6 @@ fn slice_vortex_buffer(bencher: Bencher) { }); } -#[cfg(not(codspeed))] -#[divan::bench] -fn slice_arrow_buffer(bencher: Bencher) { - let buffer = Arrow(BooleanBuffer::from_iter( - (0..SLICE_INPUT_SIZE).map(|i| i % 2 == 0), - )); - let mid = SLICE_INPUT_SIZE / 2; - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - let mut total = 0; - for offset in 0..SLICE_ITERS { - total += buffer.0.slice(mid / 2 + offset, mid / 2).len(); - } - total - }); -} - /// Below a few thousand bits the measurement is mostly divan's fixed per-iteration overhead /// rather than the popcount, so it moves when the count itself has not changed. const TRUE_COUNT_INPUT_SIZE: &[usize] = &[16_384, 65_536]; @@ -210,19 +122,6 @@ fn true_count_vortex_buffer(bencher: Bencher, length: usize) { .bench_refs(|buffer| buffer.true_count()) } -#[divan::bench(args = TRUE_COUNT_INPUT_SIZE)] -fn true_count_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter( - (0..length).map(true_count_pattern), - )); - - buffer.0.count_set_bits(); - - bencher - .with_inputs(|| &buffer) - .bench_refs(|buffer| buffer.0.count_set_bits()); -} - #[divan::bench(args = INPUT_SIZE)] fn bitwise_and_vortex_buffer(bencher: Bencher, length: usize) { let a = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -232,15 +131,6 @@ fn bitwise_and_vortex_buffer(bencher: Bencher, length: usize) { .bench_values(|(a, b)| a & b); } -#[divan::bench(args = INPUT_SIZE)] -fn bitwise_and_arrow_buffer(bencher: Bencher, length: usize) { - let a = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - let b = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 3 == 0))); - bencher - .with_inputs(|| (&a, &b)) - .bench_refs(|(a, b)| &a.0 & &b.0); -} - /// Owned-LHS AND: the left operand is a fresh, uniquely-owned `BitBuffer` each iteration, so /// `bitwise_binary_op_lhs_owned` takes the in-place (zero-allocation) fast path. Compare against /// `bitwise_and_vortex_buffer` (reference-LHS, which always allocates a result buffer). @@ -261,15 +151,6 @@ fn bitwise_or_vortex_buffer(bencher: Bencher, length: usize) { .bench_values(|(a, b)| a | b); } -#[divan::bench(args = INPUT_SIZE)] -fn bitwise_or_arrow_buffer(bencher: Bencher, length: usize) { - let a = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - let b = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 3 == 0))); - bencher - .with_inputs(|| (&a, &b)) - .bench_refs(|(a, b)| &a.0 | &b.0); -} - #[divan::bench(args = INPUT_SIZE)] fn bitwise_not_vortex_buffer(bencher: Bencher, length: usize) { bencher @@ -289,13 +170,6 @@ fn bitwise_not_vortex_buffer_mut(bencher: Bencher, length: usize) { .bench_values(|buffer| !buffer); } -#[divan::bench(args = INPUT_SIZE)] -fn bitwise_not_arrow_buffer(bencher: Bencher, length: usize) { - bencher - .with_inputs(|| Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0)))) - .bench_values(|buffer| !&buffer.0); -} - #[divan::bench(args = INPUT_SIZE)] fn iter_vortex_buffer(bencher: Bencher, length: usize) { let buffer = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -306,16 +180,6 @@ fn iter_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn iter_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - for value in buffer.0.iter() { - divan::black_box(value); - } - }); -} - #[divan::bench(args = INPUT_SIZE)] fn set_indices_vortex_buffer(bencher: Bencher, length: usize) { let buffer = BitBuffer::from_iter((0..length).map(|i| i % 2 == 0)); @@ -326,16 +190,6 @@ fn set_indices_vortex_buffer(bencher: Bencher, length: usize) { }); } -#[divan::bench(args = INPUT_SIZE)] -fn set_indices_arrow_buffer(bencher: Bencher, length: usize) { - let buffer = Arrow(BooleanBuffer::from_iter((0..length).map(|i| i % 2 == 0))); - bencher.with_inputs(|| &buffer).bench_refs(|buffer| { - for idx in buffer.0.set_indices() { - divan::black_box(idx); - } - }); -} - /// Rank-select scan over a sparse deterministic bitmap, reaching the final chunk. #[divan::bench(args = [65_536, 1_048_576])] fn select_last_vortex_buffer(bencher: Bencher, length: usize) { diff --git a/vortex-buffer/benches/vortex_buffer.rs b/vortex-buffer/benches/vortex_buffer.rs index d15287fde84..ab35bc8bc7c 100644 --- a/vortex-buffer/benches/vortex_buffer.rs +++ b/vortex-buffer/benches/vortex_buffer.rs @@ -3,9 +3,6 @@ use std::iter::Iterator; -use arrow_buffer::ArrowNativeType; -use arrow_buffer::MutableBuffer; -use arrow_buffer::ScalarBuffer; use divan::Bencher; use mimalloc::MiMalloc; use num_traits::PrimInt; @@ -20,26 +17,11 @@ fn main() { divan::main(); } -/// Wraps an arrow buffer so Divan can provide a nice name -pub struct Arrow(T); - -impl FromIterator for Arrow> { - fn from_iter>(iter: I) -> Self { - Self(ScalarBuffer::from_iter(iter)) - } -} - -impl FromIterator for Arrow { - fn from_iter>(iter: I) -> Self { - Self(MutableBuffer::from_iter(iter)) - } -} - const INPUT_SIZE: &[i32] = &[128, 1024, 2048, 16_384, 65_536]; const PUSH_N_INPUT_SIZE: &[usize] = &[2_048, 16_384, 65_536]; #[divan::bench( - types = [Arrow>,Buffer], + types = [Buffer], args = INPUT_SIZE, )] fn from_iter>(n: i32) -> B { @@ -61,21 +43,6 @@ fn push_vortex_buffer(bencher: Bencher, length: i32) { }); } -#[divan::bench(args = PUSH_INPUT_SIZE)] -fn push_arrow_buffer(bencher: Bencher, length: i32) { - bencher - .with_inputs(|| { - Arrow(MutableBuffer::with_capacity( - length as usize * size_of::(), - )) - }) - .bench_refs(|buffer| { - for idx in 0..length { - buffer.0.push(divan::black_box(idx)); - } - }); -} - #[divan::bench(types = [u8, u16, u32, u64], args = PUSH_N_INPUT_SIZE)] fn push_n_vortex_buffer(bencher: Bencher, length: usize) { bencher @@ -103,20 +70,6 @@ fn slice_tight_loop_vortex(bencher: Bencher, len: usize) { }); } -#[divan::bench(args = &[65_536usize])] -fn slice_tight_loop_arrow(bencher: Bencher, len: usize) { - let buf = ScalarBuffer::::from_iter( - (0..i32::try_from(len).vortex_expect("len fits into i32")).map(|i| i % i32::MAX), - ); - bencher.bench(|| { - let mut offset = 0; - while offset + SLICE_WINDOW <= len { - divan::black_box(buf.slice(offset, SLICE_WINDOW)); - offset += SLICE_WINDOW; - } - }); -} - /// Loops like `slice_tight_loop_vortex` above: a single empty slice is a few nanoseconds, far /// below the measurement noise floor, so the loop amortizes fixed overhead until the slice /// itself dominates. diff --git a/vortex-compute/Cargo.toml b/vortex-compute/Cargo.toml index fc90b7029df..bb3bb830b15 100644 --- a/vortex-compute/Cargo.toml +++ b/vortex-compute/Cargo.toml @@ -19,11 +19,6 @@ all-features = true vortex-buffer = { workspace = true } [dev-dependencies] -arrow-arith = { workspace = true } -arrow-array = { workspace = true } -arrow-buffer = { workspace = true } -arrow-cast = { workspace = true } -arrow-schema = { workspace = true } divan = { workspace = true } mimalloc = { workspace = true } num-traits = { workspace = true } diff --git a/vortex-compute/benches/lane_kernels.rs b/vortex-compute/benches/lane_kernels.rs index ba78c9f6d45..412a854e1f5 100644 --- a/vortex-compute/benches/lane_kernels.rs +++ b/vortex-compute/benches/lane_kernels.rs @@ -9,36 +9,14 @@ //! - propagates valid-lane overflow as `Err`, and //! - suppresses null-lane overflow without the closure ever inspecting `valid`. //! -//! Each Vortex kernel bench has a sibling `arrow_*` baseline bench using the -//! equivalent arrow-rs kernel over the same data shape, so the divan report -//! lines up side-by-side. -//! //! `lanezip_checked_add_u32` carries `#[cpu_features]`, so it is measured on every //! walltime CPU-feature leg rather than in simulation: it is one lane loop compiled -//! differently per leg. Its arrow-rs sibling is compiled only outside CodSpeed. The -//! arrow kernel is not Vortex code, so a change in its walltime is never actionable, -//! and it flipped by up to 67% between runs of identical code. Run `cargo bench` -//! locally to compare the pair under the same build flags. The cast benches stay in -//! simulation. +//! differently per leg. The Vortex cast benches stay in simulation. #![expect(clippy::unwrap_used)] -#![expect(clippy::clone_on_ref_ptr)] use std::mem::MaybeUninit; -use std::sync::Arc; - -#[cfg(not(codspeed))] -use arrow_arith::numeric::add; -use arrow_array::ArrayRef as ArrowArrayRef; -use arrow_array::Int32Array; -use arrow_array::UInt16Array; -#[cfg(not(codspeed))] -use arrow_array::UInt32Array; -use arrow_array::UInt64Array; -use arrow_buffer::NullBuffer; -use arrow_cast::CastOptions; -use arrow_cast::cast_with_options; -use arrow_schema::DataType; + use divan::Bencher; use mimalloc::MiMalloc; use num_traits::AsPrimitive; @@ -85,9 +63,6 @@ struct CastFixture { /// in-place-vs-out-of-place cast bench. values_i32: Buffer, mask: BitBuffer, - /// Validity as a plain `Vec` — the source of truth used to build both - /// the Vortex `BitBuffer` mask and the arrow `NullBuffer`. - valid: Vec, } fn cast_fixture(n: usize) -> CastFixture { @@ -118,7 +93,6 @@ fn cast_fixture(n: usize) -> CastFixture { values_u16, values_i32, mask: BitBufferMut::from_iter(raw_valid.iter().copied()).freeze(), - valid: raw_valid, } } @@ -150,20 +124,6 @@ fn try_map_into_narrow_u64_u32(bencher: Bencher, n: usize) { }); } -#[divan::bench(args = SIZES)] -fn arrow_narrow_u64_u32(bencher: Bencher, n: usize) { - let f = cast_fixture(n); - let arr: ArrowArrayRef = Arc::new(UInt64Array::from(f.values_u64.as_slice().to_vec())); - let opts = CastOptions { - safe: false, - ..CastOptions::default() - }; - - bencher - .with_inputs(|| arr.clone()) - .bench_values(|arr| cast_with_options(&arr, &DataType::UInt32, &opts).unwrap()); -} - #[divan::bench(args = SIZES)] fn map_with_mask_narrow_u64_u32(bencher: Bencher, n: usize) { let f = cast_fixture(n); @@ -219,20 +179,6 @@ fn map_with_mask_widen_u16_u32(bencher: Bencher, n: usize) { }); } -#[divan::bench(args = SIZES)] -fn arrow_widen_u16_u32(bencher: Bencher, n: usize) { - let f = cast_fixture(n); - let nulls = NullBuffer::from(f.valid.clone()); - let arr: ArrowArrayRef = Arc::new(UInt16Array::new( - f.values_u16.as_slice().to_vec().into(), - Some(nulls), - )); - - bencher.with_inputs(|| arr.clone()).bench_values(|arr| { - cast_with_options(&arr, &DataType::UInt32, &CastOptions::default()).unwrap() - }); -} - // ----------------------------------------------------------------------------- // In-place vs out-of-place fallible cast i32 → u32 (same byte width). // @@ -272,24 +218,6 @@ fn try_map_masked_in_place_narrow_i32_u32(bencher: Bencher, n: usize) { }); } -#[divan::bench(args = SIZES)] -fn arrow_narrow_i32_u32(bencher: Bencher, n: usize) { - let f = cast_fixture(n); - let nulls = NullBuffer::from(f.valid.clone()); - let arr: ArrowArrayRef = Arc::new(Int32Array::new( - f.values_i32.as_slice().to_vec().into(), - Some(nulls), - )); - let opts = CastOptions { - safe: false, - ..CastOptions::default() - }; - - bencher - .with_inputs(|| arr.clone()) - .bench_values(|arr| cast_with_options(&arr, &DataType::UInt32, &opts).unwrap()); -} - // ----------------------------------------------------------------------------- // LaneZip binary kernel: checked `u32 + u32 -> u32` over two nullable columns. // @@ -310,12 +238,6 @@ struct AddFixture { rhs: Buffer, lhs_mask: BitBuffer, rhs_mask: BitBuffer, - /// Plain `Vec` mirrors of the validity masks — used to build the arrow - /// `NullBuffer`s for the baseline bench. - #[cfg(not(codspeed))] - lhs_valid: Vec, - #[cfg(not(codspeed))] - rhs_valid: Vec, } fn add_fixture(n: usize) -> AddFixture { @@ -358,10 +280,6 @@ fn add_fixture(n: usize) -> AddFixture { rhs, lhs_mask, rhs_mask, - #[cfg(not(codspeed))] - lhs_valid, - #[cfg(not(codspeed))] - rhs_valid, } } @@ -388,24 +306,6 @@ fn lanezip_checked_add_u32(bencher: Bencher, n: usize) { }); } -#[cfg(not(codspeed))] -#[divan::bench(args = ADD_SIZES)] -fn arrow_checked_add_u32(bencher: Bencher, n: usize) { - let f = add_fixture(n); - let lhs_arr: ArrowArrayRef = Arc::new(UInt32Array::new( - f.lhs.as_slice().to_vec().into(), - Some(NullBuffer::from(f.lhs_valid.clone())), - )); - let rhs_arr: ArrowArrayRef = Arc::new(UInt32Array::new( - f.rhs.as_slice().to_vec().into(), - Some(NullBuffer::from(f.rhs_valid.clone())), - )); - - bencher - .with_inputs(|| (lhs_arr.clone(), rhs_arr.clone())) - .bench_values(|(lhs, rhs)| add(&lhs, &rhs).unwrap()); -} - // ----------------------------------------------------------------------------- // Parity assertions — must pass before divan runs benches. // ----------------------------------------------------------------------------- diff --git a/vortex-row/Cargo.toml b/vortex-row/Cargo.toml index d37f9d4ac62..6a1372ee5ed 100644 --- a/vortex-row/Cargo.toml +++ b/vortex-row/Cargo.toml @@ -28,7 +28,6 @@ vortex-session = { workspace = true } [dev-dependencies] arrow-array = { workspace = true } arrow-row = { workspace = true } -arrow-schema = { workspace = true } divan = { workspace = true } mimalloc = { workspace = true } rand = { workspace = true } diff --git a/vortex-row/benches/row_encode.rs b/vortex-row/benches/row_encode.rs index 7bf91245e9e..1795fbc5c00 100644 --- a/vortex-row/benches/row_encode.rs +++ b/vortex-row/benches/row_encode.rs @@ -4,30 +4,25 @@ #![expect( clippy::unwrap_used, clippy::expect_used, - clippy::clone_on_ref_ptr, clippy::cloned_ref_to_slice_refs, clippy::chunks_exact_to_as_chunks, clippy::redundant_clone )] -//! Row-encode throughput benchmarks comparing `arrow-row` against Vortex's [`RowEncoder`] -//! for the core canonical scenarios: a primitive i64 column, a Utf8 column, and a -//! mixed-field struct. +//! Row-encode throughput benchmarks for Vortex's [`RowEncoder`] over the core canonical +//! scenarios: a primitive i64 column, a Utf8 column, a mixed-field struct, and lists. +//! +//! Run with `--output-sizes` to print the encoded size of each list case next to `arrow-row`. use std::sync::Arc; use std::sync::LazyLock; use arrow_array::Array; -use arrow_array::Int64Array; -use arrow_array::StringArray; -use arrow_array::StructArray as ArrowStructArray; use arrow_array::builder::Int64Builder; use arrow_array::builder::ListBuilder as ArrowListBuilder; use arrow_array::builder::StringBuilder; use arrow_row::RowConverter; use arrow_row::SortField as ArrowSortField; -use arrow_schema::DataType; -use arrow_schema::Field; use divan::counter::BytesCount; use mimalloc::MiMalloc; use rand::RngExt; @@ -164,17 +159,6 @@ fn print_list_output_sizes() { // ---------- primitive_i64 ---------- -#[divan::bench] -fn primitive_i64_arrow_row(bencher: divan::Bencher) { - let v = gen_i64(N, 0); - let arr = Arc::new(Int64Array::from(v.clone())) as arrow_array::ArrayRef; - let conv = RowConverter::new(vec![ArrowSortField::new(DataType::Int64)]).unwrap(); - let bytes = (N * (1 + 8)) as u64; - bencher - .counter(BytesCount::new(bytes)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn primitive_i64_vortex(bencher: divan::Bencher) { let v = gen_i64(N, 0); @@ -189,20 +173,6 @@ fn primitive_i64_vortex(bencher: divan::Bencher) { // ---------- utf8 ---------- -#[divan::bench] -fn utf8_arrow_row(bencher: divan::Bencher) { - let words = gen_words(N, 16, 7); - let total: u64 = words - .iter() - .map(|w| 1 + (w.len().div_ceil(32) * 33) as u64) - .sum(); - let arr = Arc::new(StringArray::from(words.clone())) as arrow_array::ArrayRef; - let conv = RowConverter::new(vec![ArrowSortField::new(DataType::Utf8)]).unwrap(); - bencher - .counter(BytesCount::new(total)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn utf8_vortex(bencher: divan::Bencher) { let words = gen_words(N, 16, 7); @@ -233,31 +203,6 @@ fn struct_mixed_inputs() -> (Vec, Vec, u64) { (ids, names, total) } -#[divan::bench] -fn struct_mixed_arrow_row(bencher: divan::Bencher) { - let (ids, names, total) = struct_mixed_inputs(); - let id_arr = Arc::new(Int64Array::from(ids)) as arrow_array::ArrayRef; - let name_arr = Arc::new(StringArray::from(names)) as arrow_array::ArrayRef; - let arrow_struct = Arc::new(ArrowStructArray::from(vec![ - (Arc::new(Field::new("id", DataType::Int64, false)), id_arr), - ( - Arc::new(Field::new("name", DataType::Utf8, false)), - name_arr, - ), - ])) as arrow_array::ArrayRef; - let struct_fields = vec![ - Arc::new(Field::new("id", DataType::Int64, false)), - Arc::new(Field::new("name", DataType::Utf8, false)), - ]; - let conv = RowConverter::new(vec![ArrowSortField::new(DataType::Struct( - struct_fields.into(), - ))]) - .unwrap(); - bencher - .counter(BytesCount::new(total)) - .bench_local(|| conv.convert_columns(&[arrow_struct.clone()]).unwrap()) -} - #[divan::bench] fn struct_mixed_vortex(bencher: divan::Bencher) { let (ids, names, total) = struct_mixed_inputs(); @@ -275,16 +220,6 @@ fn struct_mixed_vortex(bencher: divan::Bencher) { // ---------- list_i64 ---------- -#[divan::bench] -fn list_i64_arrow_row(bencher: divan::Bencher) { - let (arr, _) = list_i64_inputs(); - let conv = RowConverter::new(vec![ArrowSortField::new(arr.data_type().clone())]).unwrap(); - let output_bytes = arrow_output_bytes(&conv.convert_columns(&[arr.clone()]).unwrap()); - bencher - .counter(BytesCount::new(output_bytes)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn list_i64_vortex(bencher: divan::Bencher) { let (_, list) = list_i64_inputs(); @@ -299,16 +234,6 @@ fn list_i64_vortex(bencher: divan::Bencher) { // ---------- list_utf8 ---------- -#[divan::bench] -fn list_utf8_arrow_row(bencher: divan::Bencher) { - let (arr, _) = list_utf8_inputs(); - let conv = RowConverter::new(vec![ArrowSortField::new(arr.data_type().clone())]).unwrap(); - let output_bytes = arrow_output_bytes(&conv.convert_columns(&[arr.clone()]).unwrap()); - bencher - .counter(BytesCount::new(output_bytes)) - .bench_local(|| conv.convert_columns(&[arr.clone()]).unwrap()) -} - #[divan::bench] fn list_utf8_vortex(bencher: divan::Bencher) { let (_, list) = list_utf8_inputs();